Browse Source

bugfix ominbus

jrivard 10 years ago
parent
commit
50dbebadc0
100 changed files with 2140 additions and 1721 deletions
  1. 6 1
      pwm/servlet/src/password/pwm/AppProperty.java
  2. 8 0
      pwm/servlet/src/password/pwm/AppProperty.properties
  3. 50 0
      pwm/servlet/src/password/pwm/PwmAboutProperty.java
  4. 26 12
      pwm/servlet/src/password/pwm/PwmApplication.java
  5. 7 1
      pwm/servlet/src/password/pwm/PwmConstants.java
  6. 2 1
      pwm/servlet/src/password/pwm/PwmConstants.properties
  7. 37 0
      pwm/servlet/src/password/pwm/RecoveryVerificationMethod.java
  8. 23 4
      pwm/servlet/src/password/pwm/VersionChecker.java
  9. 0 464
      pwm/servlet/src/password/pwm/bean/AboutApplicationBean.java
  10. 2 2
      pwm/servlet/src/password/pwm/config/Configuration.java
  11. 142 131
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  12. 12 322
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  13. 33 21
      pwm/servlet/src/password/pwm/config/PwmSettingCategory.java
  14. 1 1
      pwm/servlet/src/password/pwm/config/PwmSettingSyntax.java
  15. 47 0
      pwm/servlet/src/password/pwm/config/PwmSettingTemplate.java
  16. 1 2
      pwm/servlet/src/password/pwm/config/PwmSettingXml.java
  17. 5 5
      pwm/servlet/src/password/pwm/config/SettingUIFunction.java
  18. 34 13
      pwm/servlet/src/password/pwm/config/StoredConfiguration.java
  19. 72 0
      pwm/servlet/src/password/pwm/config/function/AbstractUriCertImportFunction.java
  20. 6 3
      pwm/servlet/src/password/pwm/config/function/LdapCertImportFunction.java
  21. 11 0
      pwm/servlet/src/password/pwm/config/function/NAAFCertImportFunction.java
  22. 11 0
      pwm/servlet/src/password/pwm/config/function/OAuthCertImportFunction.java
  23. 6 3
      pwm/servlet/src/password/pwm/config/function/SyslogCertImportFunction.java
  24. 16 15
      pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java
  25. 19 15
      pwm/servlet/src/password/pwm/config/option/RecoveryVerificationMethods.java
  26. 11 11
      pwm/servlet/src/password/pwm/config/profile/ForgottenPasswordProfile.java
  27. 1 1
      pwm/servlet/src/password/pwm/config/value/PasswordValue.java
  28. 25 11
      pwm/servlet/src/password/pwm/config/value/VerificationMethodValue.java
  29. 4 0
      pwm/servlet/src/password/pwm/error/ErrorInformation.java
  30. 3 1
      pwm/servlet/src/password/pwm/error/PwmError.java
  31. 1 1
      pwm/servlet/src/password/pwm/event/SyslogAuditService.java
  32. 10 4
      pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java
  33. 35 0
      pwm/servlet/src/password/pwm/http/PwmHttpRequestWrapper.java
  34. 1 0
      pwm/servlet/src/password/pwm/http/PwmRequest.java
  35. 1 1
      pwm/servlet/src/password/pwm/http/PwmResponse.java
  36. 1 1
      pwm/servlet/src/password/pwm/http/PwmSession.java
  37. 4 4
      pwm/servlet/src/password/pwm/http/bean/ConfigGuideBean.java
  38. 23 12
      pwm/servlet/src/password/pwm/http/bean/ForgottenPasswordBean.java
  39. 48 32
      pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java
  40. 15 0
      pwm/servlet/src/password/pwm/http/client/PwmHttpClientConfiguration.java
  41. 27 2
      pwm/servlet/src/password/pwm/http/client/PwmHttpClientRequest.java
  42. 2 2
      pwm/servlet/src/password/pwm/http/filter/ApplicationModeFilter.java
  43. 3 2
      pwm/servlet/src/password/pwm/http/filter/GZIPFilter.java
  44. 4 7
      pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java
  45. 0 83
      pwm/servlet/src/password/pwm/http/servlet/AdminServlet.java
  46. 1 1
      pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java
  47. 37 34
      pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java
  48. 12 20
      pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java
  49. 89 31
      pwm/servlet/src/password/pwm/http/servlet/ConfigManagerServlet.java
  50. 114 72
      pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java
  51. 32 10
      pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java
  52. 9 1
      pwm/servlet/src/password/pwm/http/servlet/OAuthConsumerServlet.java
  53. 6 1
      pwm/servlet/src/password/pwm/http/servlet/ResourceFileServlet.java
  54. 3 1
      pwm/servlet/src/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  55. 6 12
      pwm/servlet/src/password/pwm/http/tag/PasswordRequirementsTag.java
  56. 3 1
      pwm/servlet/src/password/pwm/http/tag/PwmUrlTag.java
  57. 20 2
      pwm/servlet/src/password/pwm/http/tag/PwmValueTag.java
  58. 6 2
      pwm/servlet/src/password/pwm/i18n/Admin.properties
  59. 94 99
      pwm/servlet/src/password/pwm/i18n/Config.java
  60. 4 7
      pwm/servlet/src/password/pwm/i18n/Config.properties
  61. 12 0
      pwm/servlet/src/password/pwm/i18n/ConfigEditor.java
  62. 246 0
      pwm/servlet/src/password/pwm/i18n/ConfigEditor.properties
  63. 1 0
      pwm/servlet/src/password/pwm/i18n/Display.java
  64. 11 0
      pwm/servlet/src/password/pwm/i18n/Display.properties
  65. 2 0
      pwm/servlet/src/password/pwm/i18n/Error.properties
  66. 1 1
      pwm/servlet/src/password/pwm/i18n/Health.properties
  67. 3 2
      pwm/servlet/src/password/pwm/i18n/LocaleHelper.java
  68. 125 99
      pwm/servlet/src/password/pwm/i18n/Message.java
  69. 1 1
      pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java
  70. 5 6
      pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java
  71. 2 1
      pwm/servlet/src/password/pwm/util/AbstractUrlShortener.java
  72. 2 1
      pwm/servlet/src/password/pwm/util/BasicUrlShortener.java
  73. 76 2
      pwm/servlet/src/password/pwm/util/BuildChecksumMaker.java
  74. 3 2
      pwm/servlet/src/password/pwm/util/CodeIntegrityChecker.java
  75. 100 3
      pwm/servlet/src/password/pwm/util/Helper.java
  76. 5 0
      pwm/servlet/src/password/pwm/util/JsonUtil.java
  77. 1 1
      pwm/servlet/src/password/pwm/util/PwmPasswordRuleValidator.java
  78. 39 1
      pwm/servlet/src/password/pwm/util/SecureHelper.java
  79. 4 7
      pwm/servlet/src/password/pwm/util/ServletHelper.java
  80. 9 0
      pwm/servlet/src/password/pwm/util/StringUtil.java
  81. 11 5
      pwm/servlet/src/password/pwm/util/TimeDuration.java
  82. 3 2
      pwm/servlet/src/password/pwm/util/TinyUrlShortener.java
  83. 3 3
      pwm/servlet/src/password/pwm/util/UrlShortenerService.java
  84. 77 23
      pwm/servlet/src/password/pwm/util/X509Utils.java
  85. 19 4
      pwm/servlet/src/password/pwm/util/cache/CacheService.java
  86. 48 22
      pwm/servlet/src/password/pwm/util/cli/MainClass.java
  87. 2 2
      pwm/servlet/src/password/pwm/util/cli/UserReportCommand.java
  88. 9 1
      pwm/servlet/src/password/pwm/util/localdb/Derby_LocalDB.java
  89. 32 20
      pwm/servlet/src/password/pwm/util/localdb/LocalDB.java
  90. 2 12
      pwm/servlet/src/password/pwm/util/localdb/LocalDBUtility.java
  91. 6 1
      pwm/servlet/src/password/pwm/util/logging/LocalDBLogger.java
  92. 2 2
      pwm/servlet/src/password/pwm/util/macro/ExternalRestMacro.java
  93. 1 1
      pwm/servlet/src/password/pwm/util/macro/MacroMachine.java
  94. 26 0
      pwm/servlet/src/password/pwm/util/macro/StandardMacros.java
  95. 1 1
      pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java
  96. 5 6
      pwm/servlet/src/password/pwm/util/operations/otp/LocalDbOtpOperator.java
  97. 3 0
      pwm/servlet/src/password/pwm/util/queue/AbstractQueueManager.java
  98. 1 1
      pwm/servlet/src/password/pwm/util/queue/SmsQueueManager.java
  99. 7 2
      pwm/servlet/src/password/pwm/util/stats/Statistic.java
  100. 2 1
      pwm/servlet/src/password/pwm/util/stats/StatisticsManager.java

+ 6 - 1
pwm/servlet/src/password/pwm/AppProperty.java

@@ -101,6 +101,10 @@ public enum AppProperty {
     LOCALDB_INIT_STRING                             ("localdb.initParameters"),
     MACRO_RANDOM_CHAR_MAX_LENGTH                    ("macro.randomChar.maxLength"),
     MACRO_LDAP_ATTR_CHAR_MAX_LENGTH                 ("macro.ldapAttr.maxLength"),
+    NAAF_ID                                         ("naaf.id"),
+    NAAF_SECRET                                     ("naaf.secret"),
+    NAAF_SALT_LENGTH                                ("naaf.salf.length"),
+
     
     /** Time intruder records exist in the intruder table before being deleted. */
     INTRUDER_RETENTION_TIME_MS                      ("intruder.retentionTimeMS"),
@@ -121,7 +125,6 @@ public enum AppProperty {
     LDAP_CONNECTION_TIMEOUT                         ("ldap.connection.timeoutMS"),
     LDAP_PROFILE_RETRY_DELAY                        ("ldap.profile.retryDelayMS"),
     LDAP_PROMISCUOUS_ENABLE                         ("ldap.promiscuousEnable"),
-    LDAP_SEARCH_TIMEOUT                             ("ldap.search.timeoutMS"),
     LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS       ("ldap.password.replicaCheck.initialDelayMS"),
     LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS      ("ldap.password.replicaCheck.cycleDelayMS"),
     LDAP_GUID_PATTERN                               ("ldap.guid.pattern"),
@@ -173,6 +176,7 @@ public enum AppProperty {
     REPORTING_LDAP_SEARCH_TIMEOUT                   ("reporting.ldap.searchTimeoutMs"),
     SECURITY_STRIP_INLINE_JAVASCRIPT                ("security.html.stripInlineJavascript"),
     SECURITY_HTTP_STRIP_HEADER_REGEX                ("security.http.stripHeaderRegex"),
+    SECURITY_HTTP_PROMISCUOUS_ENABLE                ("security.http.promiscuousEnable"),
     SECURITY_RESPONSES_HASH_ITERATIONS              ("security.responses.hashIterations"),
     SECURITY_INPUT_TRIM                             ("security.input.trim"),
     SECURITY_INPUT_PASSWORD_TRIM                    ("security.input.password.trim"),
@@ -180,6 +184,7 @@ public enum AppProperty {
     SECURITY_SHAREDHISTORY_HASH_ITERATIONS          ("security.sharedHistory.hashIterations"),
     SECURITY_SHAREDHISTORY_HASH_NAME                ("security.sharedHistory.hashName"),
     SECURITY_SHAREDHISTORY_CASE_INSENSITIVE         ("security.sharedHistory.caseInsensitive"),
+    SECURITY_CERTIFICATES_VALIDATE_TIMESTAMPS       ("security.certs.validateTimestamps"),
     TOKEN_REMOVAL_DELAY_MS                          ("token.removalDelayMS"),
     TOKEN_PURGE_BATCH_SIZE                          ("token.purgeBatchSize"),
     TOKEN_MAX_UNIQUE_CREATE_ATTEMPTS                ("token.maxUniqueCreateAttempts"),

+ 8 - 0
pwm/servlet/src/password/pwm/AppProperty.properties

@@ -20,6 +20,9 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 
+# Default application values.  This is not an end user modifiable file.  Values
+# can be overridden in the configuration.
+
 audit.events.emailFrom=Audit Event Notification <@DefaultEmailFromAddress@>
 audit.vault.maxRecords=100000000
 backup.path=backup
@@ -115,6 +118,9 @@ localdb.implementation=password.pwm.util.localdb.Berkeley_LocalDB
 localdb.initParameters=je.maxMemory=10000000;;;je.log.fileMax=10000000;;;je.cleaner.minUtilization=70
 macro.randomChar.maxLength=100
 macro.ldapAttr.maxLength=100
+naaf.id=41414141414141414141414141414141
+naaf.secret=876543210
+naaf.salf.length=30
 logging.devOutput.enable=false
 logging.pattern=%d{yyyy-MM-dd'T'HH:mm:ss'Z'}, %-5p, %c{2}, %m%n
 logging.file.maxSize=20MB
@@ -158,6 +164,7 @@ recaptcha.clientIframeUrl=//www.google.com/recaptcha/api/noscript
 recaptcha.validateUrl=https://www.google.com/recaptcha/api/siteverify
 security.html.stripInlineJavascript=false
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
+security.http.promiscuousEnable=false
 security.responses.hashIterations=100000
 security.input.trim=true
 security.input.password.trim=false
@@ -165,6 +172,7 @@ security.ws.rest.clientKeyLength=32
 security.sharedHistory.hashIterations=100000
 security.sharedHistory.hashName=SHA-512
 security.sharedHistory.caseInsensitive=true
+security.certs.validateTimestamps=false
 token.removalDelayMS=86400000
 token.purgeBatchSize=1000
 token.maxUniqueCreateAttempts=100

+ 50 - 0
pwm/servlet/src/password/pwm/PwmAboutProperty.java

@@ -0,0 +1,50 @@
+package password.pwm;
+
+public enum PwmAboutProperty {
+
+    app_version,
+    app_chaiApiVersion,
+    app_currentTime,
+    app_startTime,
+    app_installTime,
+    app_currentPublishedVersion,
+    app_currentPublishedVersionCheckTime,
+    app_siteUrl,
+    app_instanceID,
+    app_wordlistSize,
+    app_seedlistSize,
+    app_sharedHistorySize,
+    app_sharedHistoryOldestTime,
+    app_emailQueueSize,
+    app_emailQueueOldestTime,
+    app_smsQueueSize,
+    app_smsQueueOldestTime,
+    app_syslogQueueSize,
+    app_localDbLogSize,
+    app_localDbLogOldestTime,
+    app_localDbStorageSize,
+    app_localDbFreeSpace,
+    app_configurationRestartCounter,
+
+    build_Time,
+    build_Number,
+    build_Type,
+    build_User,
+    build_Revision,
+    build_JavaVendor,
+    build_JavaVersion,
+    build_Version,
+
+    java_memoryFree,
+    java_memoryAllocated,
+    java_memoryMax,
+    java_threadCount,
+    java_vmVendor,
+    java_vmLocation,
+    java_vmVersion,
+    java_runtimeVersion,
+    java_vmName,
+    java_osName,
+    java_osVersion,
+    java_randomAlgorithm,
+}

+ 26 - 12
pwm/servlet/src/password/pwm/PwmApplication.java

@@ -27,7 +27,6 @@ import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.bean.AboutApplicationBean;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -40,7 +39,6 @@ import password.pwm.event.AuditEvent;
 import password.pwm.event.AuditManager;
 import password.pwm.event.SystemAuditRecord;
 import password.pwm.health.HealthMonitor;
-import password.pwm.http.servlet.AdminServlet;
 import password.pwm.ldap.LdapConnectionService;
 import password.pwm.token.TokenService;
 import password.pwm.util.*;
@@ -121,7 +119,6 @@ public class PwmApplication {
     private Date installTime = new Date();
     private ErrorInformation lastLocalDBFailure = null;
 
-    private final PwmEnvironment pwmEnvironment;
     private final File applicationPath;
     private final File webInfPath;
     private final File configurationFile;
@@ -155,7 +152,6 @@ public class PwmApplication {
     {
         verifyIfApplicationPathIsSetProperly(pwmEnvironment);
 
-        this.pwmEnvironment = pwmEnvironment;
         this.configuration = pwmEnvironment.config;
         this.applicationMode = pwmEnvironment.applicationMode;
         this.applicationPath = pwmEnvironment.applicationPath;
@@ -330,8 +326,8 @@ public class PwmApplication {
         }
 
         try {
-            AboutApplicationBean aboutApplicationBean = AdminServlet.makeInfoBean(this);
-            LOGGER.trace("application info: " + JsonUtil.serialize(aboutApplicationBean));
+            Map<PwmAboutProperty,String> infoMap = Helper.makeInfoBean(this);
+            LOGGER.trace("application info: " + JsonUtil.serializeMap(infoMap));
         } catch (Exception e) {
             LOGGER.error("error generating about application bean: " + e.getMessage());
         }
@@ -709,15 +705,12 @@ public class PwmApplication {
         return webInfPath;
     }
 
-    private void verifyIfApplicationPathIsSetProperly(final PwmEnvironment pwmEnvironment)
-            throws PwmUnrecoverableException
-    {
-        final File applicationPath = pwmEnvironment.applicationPath;
-        File webInfPath = pwmEnvironment.webInfPath;
+    public static void verifyApplicationPath(final File applicationPath) throws PwmUnrecoverableException {
 
         if (applicationPath == null) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, "unable to determine valid applicationPath"));
         }
+
         LOGGER.trace("examining applicationPath of " + applicationPath.getAbsolutePath() + "");
 
         if (!applicationPath.exists()) {
@@ -732,6 +725,27 @@ public class PwmApplication {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, "unable to write to applicationPath " + applicationPath.getAbsolutePath() + ""));
         }
 
+        final File infoFile = new File(applicationPath.getAbsolutePath() + File.separator + PwmConstants.APPLICATION_PATH_INFO_FILE);
+        LOGGER.trace("checking " + infoFile.getAbsolutePath() + " status, (applicationPathType=" + PwmEnvironment.ApplicationPathType.derived + ")");
+        if (infoFile.exists()) {
+            final String errorMsg = "The file " + infoFile.getAbsolutePath() + " exists, and an applicationPath was not explicitly specified."
+                    + "  This happens when an applicationPath was previously configured, but is not now being specified."
+                    + "  An explicit applicationPath parameter must be specified, or the file can be removed if the applicationPath should be changed to the default /WEB-INF directory.";
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_STARTUP_ERROR, errorMsg));
+        } else {
+            LOGGER.trace("marker file " + infoFile.getAbsolutePath() + " does not exist");
+        }
+
+    }
+
+    private void verifyIfApplicationPathIsSetProperly(final PwmEnvironment pwmEnvironment)
+            throws PwmUnrecoverableException
+    {
+        final File applicationPath = pwmEnvironment.applicationPath;
+        File webInfPath = pwmEnvironment.webInfPath;
+
+        verifyApplicationPath(applicationPath);
+
         boolean applicationPathIsWebInfPath = false;
         if (applicationPath.equals(webInfPath)) {
             applicationPathIsWebInfPath = true;
@@ -770,7 +784,7 @@ public class PwmApplication {
                         final FileOutputStream fos = new FileOutputStream(infoFile);
                         final Properties outputProperties = new Properties();
                         outputProperties.setProperty("lastApplicationPath", applicationPath.getAbsolutePath());
-                        outputProperties.store(fos, "Marker file to record a previously specified applicationPath");
+                        outputProperties.store(fos, "Marker file to record a previously configured applicationPath");
                     } catch (IOException e) {
                         LOGGER.warn("unable to write applicationPath marker properties file " + infoFile.getAbsolutePath() + "");
                     }

+ 7 - 1
pwm/servlet/src/password/pwm/PwmConstants.java

@@ -145,6 +145,7 @@ public abstract class PwmConstants {
         ConfigFilename,
         ConfigLastModified,
         ConfigHasPassword,
+        ConfigPasswordRememberTime,
         ApplicationPath,
 
         CaptchaClientUrl,
@@ -152,7 +153,10 @@ public abstract class PwmConstants {
         CaptchaPublicKey,
 
         ForgottenPasswordChallengeSet,
-        ForgottenPasswordOptionalPageView
+        ForgottenPasswordOptionalPageView,
+        ForgottenPasswordPrompts,
+        ForgottenPasswordInstructions,
+
     }
 
 
@@ -198,6 +202,7 @@ public abstract class PwmConstants {
         RECOVER_PASSWORD_TOKEN_CHOICE("forgottenpassword-tokenchoice.jsp"),
         RECOVER_PASSWORD_ENTER_TOKEN("forgottenpassword-entertoken.jsp"),
         RECOVER_PASSWORD_ENTER_OTP("forgottenpassword-enterotp.jsp"),
+        RECOVER_PASSWORD_NAAF("forgottenpassword-naaf.jsp"),
         SETUP_RESPONSES("setupresponses.jsp"),
         SETUP_RESPONSES_CONFIRM("setupresponses-confirm.jsp"),
         SETUP_RESPONSES_HELPDESK("setupresponses-helpdesk.jsp"),
@@ -359,6 +364,7 @@ public abstract class PwmConstants {
         ContentDisposition("content-disposition"),
         ContentTransferEncoding("Content-Transfer-Encoding"),
         Accept_Encoding("Accept-Encoding"),
+        Accept_Language("Accept-Language"),
         Authorization("Authorization"),
 
         XFrameOptions("X-Frame-Options"),

+ 2 - 1
pwm/servlet/src/password/pwm/PwmConstants.properties

@@ -44,7 +44,8 @@ paramName.token=token
 defaultConfigFilename=PwmConfiguration.xml
 pwmDBLoggerMaxQueueSize=50000
 pwmDBLoggerMaxDirtyBufferMS=500
-enableEulaDisplay=false
+enableEulaDisplay=true
 databaseAccessor.keyLength=128
 missingVersionString=[Version Missing]
 applicationPathInfoFile=applicationPath.properties
+

+ 37 - 0
pwm/servlet/src/password/pwm/RecoveryVerificationMethod.java

@@ -0,0 +1,37 @@
+package password.pwm;
+
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserInfoBean;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public interface RecoveryVerificationMethod {
+    enum VerificationState {
+        INPROGRESS,
+        FAILED,
+        COMPLETE,
+    }
+
+    interface UserPrompt {
+        String getDisplayPrompt();
+        String getIdentifier();
+    }
+
+    public List<UserPrompt> getCurrentPrompts() throws PwmUnrecoverableException;
+
+    public String getCurrentDisplayInstructions();
+
+    public ErrorInformation respondToPrompts(final Map<String, String> answers) throws PwmUnrecoverableException;
+
+    public VerificationState getVerificationState();
+
+    public void init(final PwmApplication pwmApplication, final UserInfoBean userInfoBean, SessionLabel sessionLabel, Locale locale)
+            throws PwmUnrecoverableException
+            ;
+
+
+}

+ 23 - 4
pwm/servlet/src/password/pwm/VersionChecker.java

@@ -30,6 +30,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthTopic;
@@ -58,12 +59,18 @@ public class VersionChecker implements PwmService {
 
     private PwmApplication pwmApplication;
     private VersionCheckInfoCache versionCheckInfoCache;
+    private STATUS status = STATUS.CLOSED;
 
     public VersionChecker() {
     }
 
     public void init(final PwmApplication pwmApplication) {
         this.pwmApplication = pwmApplication;
+        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
+            status = STATUS.CLOSED;
+            return;
+        }
+
         if (pwmApplication.getLocalDB() != null && pwmApplication.getLocalDB().status() == LocalDB.Status.OPEN) {
             try {
                 final String versionChkInfoJson = pwmApplication.getLocalDB().get(LocalDB.DB.PWM_META,
@@ -76,6 +83,11 @@ public class VersionChecker implements PwmService {
             }
         }
 
+        if (pwmApplication.getApplicationMode() != PwmApplication.MODE.RUNNING && pwmApplication.getApplicationMode() != PwmApplication.MODE.CONFIGURATION ) {
+            LOGGER.trace("skipping init due to application mode");
+            return;
+        }
+
         if (versionCheckInfoCache != null && versionCheckInfoCache.getLastError() != null) {
             versionCheckInfoCache = null;
         }
@@ -83,10 +95,12 @@ public class VersionChecker implements PwmService {
         if (!isVersionCurrent()) {
             LOGGER.warn("this version of PWM is outdated, please check the project website for the current version");
         }
+
+        status = STATUS.OPEN;
     }
 
     public String currentVersion() {
-        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
+        if (status() != STATUS.OPEN) {
             return Display.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE, Display.Value_NotApplicable, pwmApplication.getConfig());
         }
 
@@ -102,9 +116,10 @@ public class VersionChecker implements PwmService {
     }
 
     public Date lastReadTimestamp() {
-        if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
+        if (status() != STATUS.OPEN) {
             return null;
         }
+
         try {
             final VersionCheckInfoCache versionCheckInfo = getVersionCheckInfo();
             if (versionCheckInfo != null) {
@@ -117,6 +132,10 @@ public class VersionChecker implements PwmService {
     }
 
     public boolean isVersionCurrent() {
+        if (status() != STATUS.OPEN) {
+            return true;
+        }
+
         if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
             return true;
         }
@@ -178,7 +197,7 @@ public class VersionChecker implements PwmService {
         return versionCheckInfoCache;
     }
 
-    private Map<String,String> doCurrentVersionFetch() throws IOException, URISyntaxException {
+    private Map<String,String> doCurrentVersionFetch() throws IOException, URISyntaxException, PwmUnrecoverableException {
         final URI requestURI = new URI(VERSION_CHECK_URL);
         final HttpGet httpGet = new HttpGet(requestURI.toString());
         httpGet.setHeader("Accept", PwmConstants.ContentTypeValue.json.getHeaderValue());
@@ -193,7 +212,7 @@ public class VersionChecker implements PwmService {
     }
 
     public STATUS status() {
-        return STATUS.OPEN;
+        return status;
     }
 
     public void close() {

+ 0 - 464
pwm/servlet/src/password/pwm/bean/AboutApplicationBean.java

@@ -1,464 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://code.google.com/p/pwm/
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2015 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.bean;
-
-import java.io.Serializable;
-import java.util.Date;
-
-public class AboutApplicationBean implements Serializable {
-    
-    private String version;
-    private String chaiApiVersion;
-
-    private Date currentTime;
-    private Date startTime;
-    private Date installTime;
-    
-    private String currentPublishedVersion;
-    private Date currentPublishedVersionCheckTime;
-    
-    private String siteUrl;
-    private String instanceID;
-    
-    private int wordlistSize;
-    private int seedlistSize;
-    private int sharedHistorySize;
-    private Date sharedHistoryOldestTime;
-
-    private int emailQueueSize;
-    private Date emailQueueOldestTime;
-    private int smsQueueSize;
-    private Date smsQueueOldestTime;
-    private int syslogQueueSize;
-    private Date syslogQueueOldestTime;
-    
-    private int localDbLogSize;
-    private Date localDbLogOldestTime;
-
-    private String localDbStorageSize;
-    private String localDbFreeSpace;
-    
-    private int configurationRestartCounter;
-
-    private JavaInformation javaInformation;
-    private BuildInformation buildInformation;
-
-    public static class BuildInformation implements Serializable {
-        private String buildTime;
-        private String buildNumber;
-        private String buildType;
-        private String buildUser;
-        private String buildRevision;
-        private String buildJavaVendor;
-        private String buildJavaVersion;
-        private String buildVersion;
-
-        public String getBuildTime() {
-            return buildTime;
-        }
-
-        public void setBuildTime(String buildTime) {
-            this.buildTime = buildTime;
-        }
-
-        public String getBuildNumber() {
-            return buildNumber;
-        }
-
-        public void setBuildNumber(String buildNumber) {
-            this.buildNumber = buildNumber;
-        }
-
-        public String getBuildType() {
-            return buildType;
-        }
-
-        public void setBuildType(String buildType) {
-            this.buildType = buildType;
-        }
-
-        public String getBuildUser() {
-            return buildUser;
-        }
-
-        public void setBuildUser(String buildUser) {
-            this.buildUser = buildUser;
-        }
-
-        public String getBuildRevision() {
-            return buildRevision;
-        }
-
-        public void setBuildRevision(String buildRevision) {
-            this.buildRevision = buildRevision;
-        }
-
-        public String getBuildJavaVendor() {
-            return buildJavaVendor;
-        }
-
-        public void setBuildJavaVendor(String buildJavaVendor) {
-            this.buildJavaVendor = buildJavaVendor;
-        }
-
-        public String getBuildJavaVersion() {
-            return buildJavaVersion;
-        }
-
-        public void setBuildJavaVersion(String buildJavaVersion) {
-            this.buildJavaVersion = buildJavaVersion;
-        }
-
-        public String getBuildVersion() {
-            return buildVersion;
-        }
-
-        public void setBuildVersion(String buildVersion) {
-            this.buildVersion = buildVersion;
-        }
-    }
-
-    public static class JavaInformation implements Serializable {
-        private long memoryFree;
-        private long memoryAllocated;
-        private long memoryMax;
-        private long threadCount;
-        private String vmVendor;
-        private String vmLocation;
-        private String vmVersion;
-        private String runtimeVersion;
-        private String vmName;
-        private String osName;
-        private String osVersion;
-        private String randomAlgorithm;
-
-        public JavaInformation() {
-        }
-
-        public long getMemoryFree() {
-            return memoryFree;
-        }
-
-        public void setMemoryFree(long memoryFree) {
-            this.memoryFree = memoryFree;
-        }
-
-        public long getMemoryAllocated() {
-            return memoryAllocated;
-        }
-
-        public void setMemoryAllocated(long memoryAllocated) {
-            this.memoryAllocated = memoryAllocated;
-        }
-
-        public long getMemoryMax() {
-            return memoryMax;
-        }
-
-        public void setMemoryMax(long memoryMax) {
-            this.memoryMax = memoryMax;
-        }
-
-        public long getThreadCount() {
-            return threadCount;
-        }
-
-        public void setThreadCount(long threadCount) {
-            this.threadCount = threadCount;
-        }
-
-        public String getVmVendor() {
-            return vmVendor;
-        }
-
-        public void setVmVendor(String vmVendor) {
-            this.vmVendor = vmVendor;
-        }
-
-        public String getVmLocation() {
-            return vmLocation;
-        }
-
-        public void setVmLocation(String vmLocation) {
-            this.vmLocation = vmLocation;
-        }
-
-        public String getVmVersion() {
-            return vmVersion;
-        }
-
-        public void setVmVersion(String vmVersion) {
-            this.vmVersion = vmVersion;
-        }
-
-        public String getRuntimeVersion() {
-            return runtimeVersion;
-        }
-
-        public void setRuntimeVersion(String runtimeVersion) {
-            this.runtimeVersion = runtimeVersion;
-        }
-
-        public String getVmName() {
-            return vmName;
-        }
-
-        public void setVmName(String vmName) {
-            this.vmName = vmName;
-        }
-
-        public String getOsName() {
-            return osName;
-        }
-
-        public void setOsName(String osName) {
-            this.osName = osName;
-        }
-
-        public String getOsVersion() {
-            return osVersion;
-        }
-
-        public void setOsVersion(String osVersion) {
-            this.osVersion = osVersion;
-        }
-
-        public String getRandomAlgorithm() {
-            return randomAlgorithm;
-        }
-
-        public void setRandomAlgorithm(String randomAlgorithm) {
-            this.randomAlgorithm = randomAlgorithm;
-        }
-    }
-    
-    ///////////////////////////////
-
-    public String getVersion() {
-        return version;
-    }
-
-    public void setVersion(String version) {
-        this.version = version;
-    }
-
-    public String getChaiApiVersion() {
-        return chaiApiVersion;
-    }
-
-    public void setChaiApiVersion(String chaiApiVersion) {
-        this.chaiApiVersion = chaiApiVersion;
-    }
-
-    public Date getCurrentTime() {
-        return currentTime;
-    }
-
-    public void setCurrentTime(Date currentTime) {
-        this.currentTime = currentTime;
-    }
-
-    public Date getStartTime() {
-        return startTime;
-    }
-
-    public void setStartTime(Date startTime) {
-        this.startTime = startTime;
-    }
-
-    public Date getInstallTime() {
-        return installTime;
-    }
-
-    public void setInstallTime(Date installTime) {
-        this.installTime = installTime;
-    }
-
-    public String getCurrentPublishedVersion() {
-        return currentPublishedVersion;
-    }
-
-    public void setCurrentPublishedVersion(String currentPublishedVersion) {
-        this.currentPublishedVersion = currentPublishedVersion;
-    }
-
-    public Date getCurrentPublishedVersionCheckTime() {
-        return currentPublishedVersionCheckTime;
-    }
-
-    public void setCurrentPublishedVersionCheckTime(Date currentPublishedVersionCheckTime) {
-        this.currentPublishedVersionCheckTime = currentPublishedVersionCheckTime;
-    }
-
-    public String getSiteUrl() {
-        return siteUrl;
-    }
-
-    public void setSiteUrl(String siteUrl) {
-        this.siteUrl = siteUrl;
-    }
-
-    public String getInstanceID() {
-        return instanceID;
-    }
-
-    public void setInstanceID(String instanceID) {
-        this.instanceID = instanceID;
-    }
-
-    public int getWordlistSize() {
-        return wordlistSize;
-    }
-
-    public void setWordlistSize(int wordlistSize) {
-        this.wordlistSize = wordlistSize;
-    }
-
-    public int getSeedlistSize() {
-        return seedlistSize;
-    }
-
-    public void setSeedlistSize(int seedlistSize) {
-        this.seedlistSize = seedlistSize;
-    }
-
-    public int getSharedHistorySize() {
-        return sharedHistorySize;
-    }
-
-    public void setSharedHistorySize(int sharedHistorySize) {
-        this.sharedHistorySize = sharedHistorySize;
-    }
-
-    public int getEmailQueueSize() {
-        return emailQueueSize;
-    }
-
-    public void setEmailQueueSize(int emailQueueSize) {
-        this.emailQueueSize = emailQueueSize;
-    }
-
-    public Date getEmailQueueOldestTime() {
-        return emailQueueOldestTime;
-    }
-
-    public void setEmailQueueOldestTime(Date emailQueueOldestTime) {
-        this.emailQueueOldestTime = emailQueueOldestTime;
-    }
-
-    public int getSmsQueueSize() {
-        return smsQueueSize;
-    }
-
-    public void setSmsQueueSize(int smsQueueSize) {
-        this.smsQueueSize = smsQueueSize;
-    }
-
-    public Date getSmsQueueOldestTime() {
-        return smsQueueOldestTime;
-    }
-
-    public void setSmsQueueOldestTime(Date smsQueueOldestTime) {
-        this.smsQueueOldestTime = smsQueueOldestTime;
-    }
-
-    public int getSyslogQueueSize() {
-        return syslogQueueSize;
-    }
-
-    public void setSyslogQueueSize(int syslogQueueSize) {
-        this.syslogQueueSize = syslogQueueSize;
-    }
-
-    public Date getSyslogQueueOldestTime() {
-        return syslogQueueOldestTime;
-    }
-
-    public void setSyslogQueueOldestTime(Date syslogQueueOldestTime) {
-        this.syslogQueueOldestTime = syslogQueueOldestTime;
-    }
-
-    public int getLocalDbLogSize() {
-        return localDbLogSize;
-    }
-
-    public void setLocalDbLogSize(int localDbLogSize) {
-        this.localDbLogSize = localDbLogSize;
-    }
-
-    public Date getLocalDbLogOldestTime() {
-        return localDbLogOldestTime;
-    }
-
-    public void setLocalDbLogOldestTime(Date localDbLogOldestTime) {
-        this.localDbLogOldestTime = localDbLogOldestTime;
-    }
-
-    public Date getSharedHistoryOldestTime() {
-        return sharedHistoryOldestTime;
-    }
-
-    public void setSharedHistoryOldestTime(Date sharedHistoryOldestTime) {
-        this.sharedHistoryOldestTime = sharedHistoryOldestTime;
-    }
-
-    public String getLocalDbStorageSize() {
-        return localDbStorageSize;
-    }
-
-    public void setLocalDbStorageSize(String localDbStorageSize) {
-        this.localDbStorageSize = localDbStorageSize;
-    }
-
-    public String getLocalDbFreeSpace() {
-        return localDbFreeSpace;
-    }
-
-    public void setLocalDbFreeSpace(String localDbFreeSpace) {
-        this.localDbFreeSpace = localDbFreeSpace;
-    }
-
-    public int getConfigurationRestartCounter() {
-        return configurationRestartCounter;
-    }
-
-    public void setConfigurationRestartCounter(int configurationRestartCounter) {
-        this.configurationRestartCounter = configurationRestartCounter;
-    }
-
-    public JavaInformation getJavaInformation() {
-        return javaInformation;
-    }
-
-    public void setJavaInformation(JavaInformation javaInformation) {
-        this.javaInformation = javaInformation;
-    }
-
-    public BuildInformation getBuildInformation() {
-        return buildInformation;
-    }
-
-    public void setBuildInformation(BuildInformation buildInformation) {
-        this.buildInformation = buildInformation;
-    }
-}

+ 2 - 2
pwm/servlet/src/password/pwm/config/Configuration.java

@@ -296,7 +296,7 @@ public class Configuration implements Serializable, SettingReader {
                 throw new IllegalArgumentException("may not read optionlist value for setting: " + setting.toString());
             }
 
-            final Set<E> returnSet = new HashSet<>();
+            final Set<E> returnSet = new LinkedHashSet<>();
             final Set<String> strValues = (Set<String>)value.toNativeObject();
             for (final String strValue : strValues) {
                 try {
@@ -620,7 +620,7 @@ public class Configuration implements Serializable, SettingReader {
         }
     }
 
-    public PwmSetting.Template getTemplate() {
+    public PwmSettingTemplate getTemplate() {
         return storedConfiguration.getTemplate();
     }
 

+ 142 - 131
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -31,6 +31,7 @@ import password.pwm.config.value.ValueFactory;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Config;
+import password.pwm.i18n.ConfigEditor;
 import password.pwm.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -195,13 +196,15 @@ public enum PwmSetting {
             "ldap.user.group.attribute", PwmSettingSyntax.STRING, PwmSettingCategory.LDAP_PROFILE),
     LDAP_GROUP_LABEL_ATTRIBUTE(
             "ldap.group.label.attribute", PwmSettingSyntax.STRING, PwmSettingCategory.LDAP_PROFILE),
+    LDAP_SEARCH_TIMEOUT(
+            "ldap.search.timeoutSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.LDAP_PROFILE),
 
 
     // ldap global settings
     LDAP_PROFILE_LIST(
             "ldap.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.GENERAL),
     LDAP_IDLE_TIMEOUT(
-            "ldap.idleTimeout", PwmSettingSyntax.NUMERIC, PwmSettingCategory.LDAP_GLOBAL),
+            "ldap.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.LDAP_GLOBAL),
     DEFAULT_OBJECT_CLASSES(
             "ldap.defaultObjectClasses", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.LDAP_GLOBAL),
     LDAP_FOLLOW_REFERRALS(
@@ -820,41 +823,41 @@ public enum PwmSetting {
 
     // edirectory settings
     EDIRECTORY_ENABLE_NMAS(
-            "ldap.edirectory.enableNmas", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.enableNmas", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_STORE_NMAS_RESPONSES(
-            "ldap.edirectory.storeNmasResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.storeNmasResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_USE_NMAS_RESPONSES(
-            "ldap.edirectory.useNmasResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.useNmasResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_READ_USER_PWD(
-            "ldap.edirectory.readUserPwd", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.readUserPwd", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_PWD_MGT_WEBSERVICE_URL(
-            "ldap.edirectory.ws.pwdMgtURL", PwmSettingSyntax.STRING, PwmSettingCategory.EDIR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.ws.pwdMgtURL", PwmSettingSyntax.STRING, PwmSettingCategory.EDIR_SETTINGS, PwmSettingTemplate.NOVL),
 
     EDIRECTORY_READ_CHALLENGE_SET(
-            "ldap.edirectory.readChallengeSets", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_CR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.readChallengeSets", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_CR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_CR_MIN_RANDOM_DURING_SETUP(
-            "ldap.edirectory.cr.minRandomDuringSetup", PwmSettingSyntax.NUMERIC, PwmSettingCategory.EDIR_CR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.cr.minRandomDuringSetup", PwmSettingSyntax.NUMERIC, PwmSettingCategory.EDIR_CR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_CR_APPLY_WORDLIST(
-            "ldap.edirectory.cr.applyWordlist", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_CR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.cr.applyWordlist", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.EDIR_CR_SETTINGS, PwmSettingTemplate.NOVL),
     EDIRECTORY_CR_MAX_QUESTION_CHARS_IN__ANSWER(
-            "ldap.edirectory.cr.maxQuestionCharsInAnswer", PwmSettingSyntax.NUMERIC, PwmSettingCategory.EDIR_CR_SETTINGS, Template.NOVL),
+            "ldap.edirectory.cr.maxQuestionCharsInAnswer", PwmSettingSyntax.NUMERIC, PwmSettingCategory.EDIR_CR_SETTINGS, PwmSettingTemplate.NOVL),
 
 
     // active directory
     AD_USE_PROXY_FOR_FORGOTTEN(
-            "ldap.ad.proxyForgotten", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, Template.AD),
+            "ldap.ad.proxyForgotten", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, PwmSettingTemplate.AD),
     AD_ALLOW_AUTH_REQUIRE_NEW_PWD(
-            "ldap.ad.allowAuth.requireNewPassword", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, Template.AD),
+            "ldap.ad.allowAuth.requireNewPassword", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, PwmSettingTemplate.AD),
     AD_ALLOW_AUTH_EXPIRED(
-            "ldap.ad.allowAuth.expired", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, Template.AD),
+            "ldap.ad.allowAuth.expired", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, PwmSettingTemplate.AD),
     AD_ENFORCE_PW_HISTORY_ON_SET(
-            "ldap.ad.enforcePwHistoryOnSet", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, Template.AD),
+            "ldap.ad.enforcePwHistoryOnSet", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVE_DIRECTORY, PwmSettingTemplate.AD),
 
     // active directory
     ORACLE_DS_ENABLE_MANIP_ALLOWCHANGETIME(
-            "ldap.oracleDS.enable.manipAllowChangeTime", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ORACLE_DS, Template.ORACLE_DS),
+            "ldap.oracleDS.enable.manipAllowChangeTime", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ORACLE_DS, PwmSettingTemplate.ORACLE_DS),
     ORACLE_DS_ALLOW_AUTH_REQUIRE_NEW_PWD(
-            "ldap.oracleDS.allowAuth.requireNewPassword", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ORACLE_DS, Template.ORACLE_DS),
+            "ldap.oracleDS.allowAuth.requireNewPassword", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ORACLE_DS, PwmSettingTemplate.ORACLE_DS),
 
 
     // helpdesk profile
@@ -886,8 +889,6 @@ public enum PwmSetting {
             "helpdesk.actions", PwmSettingSyntax.ACTION, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_IDLE_TIMEOUT_SECONDS(
             "helpdesk.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.HELPDESK_PROFILE),
-
-
     HELPDESK_ENABLE_UNLOCK(
             "helpdesk.enableUnlock", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_ENFORCE_PASSWORD_POLICY(
@@ -910,6 +911,8 @@ public enum PwmSetting {
             "helpdesk.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_ENABLE_OTP_VERIFY(
             "helpdesk.otp.verify", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
+    HELPDESK_PASSWORD_MASKVALUE(
+            "helpdesk.setPassword.maskValue", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
 
 
 
@@ -956,6 +959,9 @@ public enum PwmSetting {
             "oauth.idserver.codeResolveUrl", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH),
     OAUTH_ID_ATTRIBUTES_URL(
             "oauth.idserver.attributesUrl", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH),
+    OAUTH_ID_CERTIFICATE(
+            "oauth.idserver.serverCerts", PwmSettingSyntax.X509CERT, PwmSettingCategory.OAUTH),
+
     OAUTH_ID_CLIENTNAME(
             "oauth.idserver.clientName", PwmSettingSyntax.STRING, PwmSettingCategory.OAUTH),
     OAUTH_ID_SECRET(
@@ -1000,6 +1006,16 @@ public enum PwmSetting {
             "webservice.userAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.REST_CLIENT),
 
 
+    // NAAF
+    NAAF_WS_URL(
+            "naaf.ws.url", PwmSettingSyntax.STRING, PwmSettingCategory.NAAF),
+    NAAF_WS_CERTIFICATE(
+            "naaf.ws.serverCerts", PwmSettingSyntax.X509CERT, PwmSettingCategory.NAAF),
+    NAAF_USER_IDENTIFIER(
+            "naaf.userIdentifier", PwmSettingSyntax.STRING, PwmSettingCategory.NAAF),
+    NAAF_METHODS(
+            "naaf.requiredMethods", PwmSettingSyntax.OPTIONLIST, PwmSettingCategory.NAAF),
+
 
     // deprecated.
     PASSWORD_POLICY_AD_COMPLEXITY(
@@ -1013,25 +1029,22 @@ public enum PwmSetting {
 
     ;
 
-// ------------------------------ STATICS ------------------------------
-
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmSetting.class);
 
-
-// ------------------------------ FIELDS ------------------------------
-
-    private static class Static {
-        private static final Pattern DEFAULT_REGEX = Pattern.compile(".*", Pattern.DOTALL);
-    }
-
     private final String key;
     private final PwmSettingSyntax syntax;
     private final PwmSettingCategory category;
-    private final Set<Template> templates;
+    private final Set<PwmSettingTemplate> templates;
+
+    private Map<PwmSettingTemplate, StoredValue> defaultValues;
+    private Map<String,String> options;
+    private String placeholder;
+    private Boolean required;
+    private Boolean hidden;
+    private Integer level;
+    private Pattern pattern;
 
 
-    private final Map<Template, StoredValue>    CACHE_DEFAULT_VALUES = new HashMap<>();
-    private final Map<Locale,String>            CACHE_LABELS = new HashMap<>();
 
 // --------------------------- CONSTRUCTORS ---------------------------
 
@@ -1039,13 +1052,13 @@ public enum PwmSetting {
             final String key,
             final PwmSettingSyntax syntax,
             final PwmSettingCategory category,
-            final Template... templates
+            final PwmSettingTemplate... templates
     ) {
         this.key = key;
         this.syntax = syntax;
         this.category = category;
-        final Template[] temps = (templates == null || templates.length == 0) ? Template.values() : templates;
-        this.templates = Collections.unmodifiableSet(new HashSet(Arrays.asList(temps)));
+        final PwmSettingTemplate[] temps = (templates == null || templates.length == 0) ? PwmSettingTemplate.values() : templates;
+        this.templates = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(temps)));
     }
 
 
@@ -1068,46 +1081,52 @@ public enum PwmSetting {
         return syntax;
     }
 
-    public Set<Template> getTemplates() {
+    public Set<PwmSettingTemplate> getTemplates() {
         return templates;
     }
 
 
     // -------------------------- OTHER METHODS --------------------------
 
-    public StoredValue getDefaultValue(final Template template)
-            throws PwmOperationalException, PwmUnrecoverableException {
-        if (!CACHE_DEFAULT_VALUES.containsKey(template)) {
-            if (this.getSyntax() == PwmSettingSyntax.PASSWORD) {
-                CACHE_DEFAULT_VALUES.put(template, new PasswordValue(null));
-            } else {
-                final Element settingElement = PwmSettingXml.readSettingXml(this);
-                final XPathFactory xpfac = XPathFactory.instance();
-                Element defaultElement = null;
-                if (template != null) {
-                    XPathExpression xp = xpfac.compile("default[@template=\"" + template.toString() + "\"]");
-                    defaultElement = (Element) xp.evaluateFirst(settingElement);
-                }
-                if (defaultElement == null) {
-                    XPathExpression xp = xpfac.compile("default[not(@template)]");
-                    defaultElement = (Element) xp.evaluateFirst(settingElement);
-                }
-                if (defaultElement == null) {
-                    throw new IllegalStateException("no default value for setting " + this.getKey());
+    public StoredValue getDefaultValue(final PwmSettingTemplate templates)
+            throws PwmOperationalException, PwmUnrecoverableException
+    {
+        if (defaultValues == null) {
+            final Map<PwmSettingTemplate, StoredValue> returnObj = new HashMap<>();
+            for (final PwmSettingTemplate loopTemplate : PwmSettingTemplate.values()) {
+                if (this.getSyntax() == PwmSettingSyntax.PASSWORD) {
+                    returnObj.put(loopTemplate, new PasswordValue(null));
+                } else {
+                    final Element settingElement = PwmSettingXml.readSettingXml(this);
+                    final XPathFactory xpfac = XPathFactory.instance();
+                    Element defaultElement = null;
+                    if (loopTemplate != null) {
+                        XPathExpression xp = xpfac.compile("default[@template=\"" + loopTemplate.toString() + "\"]");
+                        defaultElement = (Element) xp.evaluateFirst(settingElement);
+                    }
+                    if (defaultElement == null) {
+                        XPathExpression xp = xpfac.compile("default[not(@template)]");
+                        defaultElement = (Element) xp.evaluateFirst(settingElement);
+                    }
+                    if (defaultElement == null) {
+                        throw new IllegalStateException("no default value for setting " + this.getKey());
+                    }
+                    returnObj.put(loopTemplate, ValueFactory.fromXmlValues(this, defaultElement, this.getKey()));
                 }
-                CACHE_DEFAULT_VALUES.put(template, ValueFactory.fromXmlValues(this, defaultElement, this.getKey()));
+
             }
+            defaultValues = returnObj;
         }
-        return CACHE_DEFAULT_VALUES.get(template);
+        return defaultValues.get(templates);
     }
 
-    public Map<Template, String> getDefaultValueDebugStrings(final boolean prettyPrint, final Locale locale)
+    public Map<PwmSettingTemplate, String> getDefaultValueDebugStrings(final boolean prettyPrint, final Locale locale)
             throws PwmOperationalException, PwmUnrecoverableException {
-        final Map<Template, String> returnObj = new LinkedHashMap<>();
-        final String defaultDebugStr = this.getDefaultValue(Template.DEFAULT).toDebugString(prettyPrint, locale);
-        returnObj.put(Template.DEFAULT, defaultDebugStr);
-        for (final Template template : Template.values()) {
-            if (template != Template.DEFAULT) {
+        final Map<PwmSettingTemplate, String> returnObj = new LinkedHashMap<>();
+        final String defaultDebugStr = this.getDefaultValue(PwmSettingTemplate.DEFAULT).toDebugString(prettyPrint, locale);
+        returnObj.put(PwmSettingTemplate.DEFAULT, defaultDebugStr);
+        for (final PwmSettingTemplate template : PwmSettingTemplate.values()) {
+            if (template != PwmSettingTemplate.DEFAULT) {
                 final String debugStr = this.getDefaultValue(template).toDebugString(prettyPrint, locale);
                 if (!defaultDebugStr.equals(debugStr)) {
                     returnObj.put(template, debugStr);
@@ -1118,101 +1137,99 @@ public enum PwmSetting {
     }
 
     public Map<String, String> getOptions() {
-        final Element settingElement = PwmSettingXml.readSettingXml(this);
-        final Element optionsElement = settingElement.getChild("options");
-        final Map<String, String> returnList = new LinkedHashMap<>();
-        if (optionsElement != null) {
-            final List<Element> optionElements = optionsElement.getChildren("option");
-            if (optionElements != null) {
-                for (Element optionElement : optionElements) {
-                    if (optionElement.getAttribute("value") == null) {
-                        throw new IllegalStateException("option element is missing 'value' attribute for key " + this.getKey());
+        if (options == null) {
+            final Map<String, String> returnList = new LinkedHashMap<>();
+            final Element settingElement = PwmSettingXml.readSettingXml(this);
+            final Element optionsElement = settingElement.getChild("options");
+            if (optionsElement != null) {
+                final List<Element> optionElements = optionsElement.getChildren("option");
+                if (optionElements != null) {
+                    for (Element optionElement : optionElements) {
+                        if (optionElement.getAttribute("value") == null) {
+                            throw new IllegalStateException("option element is missing 'value' attribute for key " + this.getKey());
+                        }
+                        returnList.put(optionElement.getAttribute("value").getValue(), optionElement.getValue());
                     }
-                    returnList.put(optionElement.getAttribute("value").getValue(), optionElement.getValue());
                 }
             }
+            options = Collections.unmodifiableMap(returnList);
         }
 
-        return Collections.unmodifiableMap(returnList);
+        return options;
     }
 
     public String getLabel(final Locale locale) {
-        if (!CACHE_LABELS.containsKey(locale)) {
-            final Element settingElement = PwmSettingXml.readSettingXml(this);
-            if (settingElement == null) {
-                throw new IllegalStateException("missing Setting value for setting " + this.getKey());
-            }
-            final Element labelElement = settingElement.getChild("label");
-            String labelText = labelElement.getText();
-            CACHE_LABELS.put(locale,labelText);
-        }
-        return CACHE_LABELS.get(locale);
+        final String key = "Setting_Label_" + this.getKey();
+        return LocaleHelper.getLocalizedMessage(locale, key, null, ConfigEditor.class);
     }
 
     public String getDescription(final Locale locale) {
-        final Element settingElement = PwmSettingXml.readSettingXml(this);
-        final Element descriptionElement = settingElement.getChild("description");
-        final String storedText = descriptionElement.getText();
+        final String key = "Setting_Description_" + this.getKey();
+        final String storedText = LocaleHelper.getLocalizedMessage(locale, key, null, ConfigEditor.class);
         final MacroMachine macroMachine = MacroMachine.forStatic();
-        final String value = macroMachine.expandMacros(storedText);
-        return value;
+        return macroMachine.expandMacros(storedText);
     }
 
     public String getPlaceholder(final Locale locale) {
-        Element placeHolderElement = PwmSettingXml.readSettingXml(this);
-        Element placeholder = placeHolderElement.getChild("placeholder");
-        return placeholder != null ? placeholder.getText() : "";
+        if (placeholder == null) {
+            Element settingElement = PwmSettingXml.readSettingXml(this);
+            Element placeholderElement = settingElement.getChild("placeholder");
+            placeholder = placeholderElement != null ? placeholderElement.getText() : "";
+        }
+        return placeholder;
     }
 
     public boolean isRequired() {
-        final Element settingElement = PwmSettingXml.readSettingXml(this);
-        final Attribute requiredAttribute = settingElement.getAttribute("required");
-        return requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+        if (required == null) {
+            final Element settingElement = PwmSettingXml.readSettingXml(this);
+            final Attribute requiredAttribute = settingElement.getAttribute("required");
+            required = requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+        }
+        return required;
     }
 
     public boolean isHidden() {
-        final Element settingElement = PwmSettingXml.readSettingXml(this);
-        final Attribute requiredAttribute = settingElement.getAttribute("hidden");
-        return requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+        if (hidden == null) {
+            final Element settingElement = PwmSettingXml.readSettingXml(this);
+            final Attribute requiredAttribute = settingElement.getAttribute("hidden");
+            hidden = requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+        }
+        return hidden;
     }
 
     public int getLevel() {
-        final Element settingElement = PwmSettingXml.readSettingXml(this);
-        final Attribute levelAttribute = settingElement.getAttribute("level");
-        return levelAttribute != null ? Integer.parseInt(levelAttribute.getValue()) : 0;
-    }
-
-    public Pattern getRegExPattern() {
-        Element settingNode = PwmSettingXml.readSettingXml(this);
-        Element regexNode = settingNode.getChild("regex");
-        if (regexNode == null) {
-            return Static.DEFAULT_REGEX;
-        }
-        try {
-            return Pattern.compile(regexNode.getText());
-        } catch (PatternSyntaxException e) {
-            final String errorMsg = "error compiling regex constraints for setting " + this.toString() + ", error: " + e.getMessage();
-            LOGGER.error(errorMsg, e);
-            throw new IllegalStateException(errorMsg, e);
+        if (level == null) {
+            final Element settingElement = PwmSettingXml.readSettingXml(this);
+            final Attribute levelAttribute = settingElement.getAttribute("level");
+            level = levelAttribute != null ? Integer.parseInt(levelAttribute.getValue()) : 0;
         }
+        return level;
     }
 
-    public enum Template {
-        NOVL,
-        AD,
-        ORACLE_DS,
-        DEFAULT,;
-
-        public String getLabel(final Locale locale) {
-            Element categoryElement = PwmSettingXml.readTemplateXml(this);
-            Element labelElement = categoryElement.getChild("label");
-            return labelElement.getText();
+    public Pattern getRegExPattern() {
+        if (pattern == null) {
+            Element settingNode = PwmSettingXml.readSettingXml(this);
+            Element regexNode = settingNode.getChild("regex");
+            if (regexNode != null) {
+                try {
+                    pattern = Pattern.compile(regexNode.getText());
+                } catch (PatternSyntaxException e) {
+                    final String errorMsg = "error compiling regex constraints for setting " + this.toString() + ", error: " + e.getMessage();
+                    LOGGER.error(errorMsg, e);
+                    throw new IllegalStateException(errorMsg, e);
+                }
+            }
+            if (pattern == null) {
+                final Pattern DEFAULT_REGEX = Pattern.compile(".*", Pattern.DOTALL);
+                pattern = DEFAULT_REGEX;
+            }
         }
+        return pattern;
 
     }
 
     public static Map<PwmSettingCategory, List<PwmSetting>> valuesByFilter(
-            final Template template,
+            final PwmSettingTemplate template,
             final PwmSettingCategory parent,
             final int level) {
         final List<PwmSetting> settingList = new ArrayList<>(Arrays.asList(PwmSetting.values()));
@@ -1258,13 +1275,7 @@ public enum PwmSetting {
             final Locale locale
     ) {
         final String SEPARATOR = LocaleHelper.getLocalizedMessage(locale, Config.Display_SettingNavigationSeparator, null);
-        final StringBuilder sb = new StringBuilder();
-        sb.append(this.getCategory().toMenuLocationDebug(profileID, locale));
-
-        sb.append(SEPARATOR);
-        sb.append(this.getLabel(locale));
-
-        return sb.toString();
+        return this.getCategory().toMenuLocationDebug(profileID, locale) + SEPARATOR + this.getLabel(locale);
     }
 
     public enum SettingStat {

File diff suppressed because it is too large
+ 12 - 322
pwm/servlet/src/password/pwm/config/PwmSetting.xml


+ 33 - 21
pwm/servlet/src/password/pwm/config/PwmSettingCategory.java

@@ -25,6 +25,7 @@ package password.pwm.config;
 import org.jdom2.Attribute;
 import org.jdom2.Element;
 import password.pwm.i18n.Config;
+import password.pwm.i18n.ConfigEditor;
 import password.pwm.i18n.LocaleHelper;
 
 import java.util.*;
@@ -82,6 +83,7 @@ public enum PwmSettingCategory {
 
     DATABASE                    (SETTINGS),
     REPORTING                   (SETTINGS),
+    NAAF                        (SETTINGS),
     
     SSO                         (SETTINGS),
     OAUTH                       (SSO),
@@ -125,6 +127,12 @@ public enum PwmSettingCategory {
     private static final Map<PwmSettingCategory,PwmSetting> CACHE_PROFILE_SETTING = new HashMap<>();
     private static List<PwmSettingCategory> cached_sortedSettings;
 
+    private String label;
+    private String description;
+    private Integer level;
+    private Boolean hidden;
+
+
     PwmSettingCategory(PwmSettingCategory parent) {
         this.parent = parent;
     }
@@ -150,33 +158,31 @@ public enum PwmSettingCategory {
     }
 
     public String getLabel(final Locale locale) {
-        final Element categoryElement = PwmSettingXml.readCategoryXml(this);
-        if (categoryElement == null) {
-            throw new IllegalStateException("missing descriptor element for category " + this.toString());
-        }
-        final Element labelElement = categoryElement.getChild("label");
-        if (labelElement == null) {
-            throw new IllegalStateException("missing descriptor label for category " + this.toString());
-        }
-        return labelElement.getText();
+        final String key = "Category_Label_" + this.getKey();
+        return LocaleHelper.getLocalizedMessage(locale, key, null, ConfigEditor.class);
     }
 
     public String getDescription(final Locale locale) {
-        Element categoryElement = PwmSettingXml.readCategoryXml(this);
-        Element description = categoryElement.getChild("description");
-        return description == null ? "" : description.getText();
+        final String key = "Category_Description_" + this.getKey();
+        return LocaleHelper.getLocalizedMessage(locale, key, null, ConfigEditor.class);
     }
 
     public int getLevel() {
-        final Element settingElement = PwmSettingXml.readCategoryXml(this);
-        final Attribute levelAttribute = settingElement.getAttribute("level");
-        return levelAttribute != null ? Integer.parseInt(levelAttribute.getValue()) : 0;
+        if (level == null) {
+            final Element settingElement = PwmSettingXml.readCategoryXml(this);
+            final Attribute levelAttribute = settingElement.getAttribute("level");
+            level = levelAttribute != null ? Integer.parseInt(levelAttribute.getValue()) : 0;
+        }
+        return level;
     }
 
     public boolean isHidden() {
-        final Element settingElement = PwmSettingXml.readCategoryXml(this);
-        final Attribute requiredAttribute = settingElement.getAttribute("hidden");
-        return requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+        if (hidden == null) {
+            final Element settingElement = PwmSettingXml.readCategoryXml(this);
+            final Attribute hiddenElement = settingElement.getAttribute("hidden");
+            hidden = hiddenElement != null && "true".equalsIgnoreCase(hiddenElement.getValue());
+        }
+        return hidden;
     }
 
     public boolean isTopCategory() {
@@ -254,9 +260,15 @@ public enum PwmSettingCategory {
             nextCategory = nextCategory.getParent();
         }
 
-        if (profileID != null) {
-            sb.append(SEPARATOR);
-            sb.append(profileID);
+        if (this.hasProfiles()) {
+            if (profileID != null) {
+                sb.append(SEPARATOR);
+                sb.append(profileID);
+            } else {
+                final String NULL_PROFILE = LocaleHelper.getLocalizedMessage(locale, Config.Display_SettingNavigationNullProfile, null);
+                sb.append(SEPARATOR);
+                sb.append(NULL_PROFILE);
+            }
         }
 
         return sb.toString();

+ 1 - 1
pwm/servlet/src/password/pwm/config/PwmSettingSyntax.java

@@ -51,7 +51,7 @@ public enum PwmSettingSyntax {
 
     private StoredValue.StoredValueFactory storedValueImpl;
 
-    private PwmSettingSyntax(StoredValue.StoredValueFactory storedValueImpl) {
+    PwmSettingSyntax(StoredValue.StoredValueFactory storedValueImpl) {
         this.storedValueImpl = storedValueImpl;
     }
 

+ 47 - 0
pwm/servlet/src/password/pwm/config/PwmSettingTemplate.java

@@ -0,0 +1,47 @@
+package password.pwm.config;
+
+import org.jdom2.Attribute;
+import org.jdom2.Element;
+import password.pwm.i18n.ConfigEditor;
+import password.pwm.i18n.LocaleHelper;
+
+import java.util.*;
+
+public enum PwmSettingTemplate {
+    NOVL,
+    AD,
+    ORACLE_DS,
+    DEFAULT,
+    NOVL_IDM,
+
+    ;
+
+    public String getLabel(final Locale locale) {
+        final String key = "Template_Label_" + this.toString();
+        return LocaleHelper.getLocalizedMessage(locale, key, null, ConfigEditor.class);
+    }
+
+    public boolean isHidden() {
+        final Element templateElement = readTemplateElement(this);
+        final Attribute requiredAttribute = templateElement.getAttribute("hidden");
+        return requiredAttribute != null && "true".equalsIgnoreCase(requiredAttribute.getValue());
+    }
+
+    private static Element readTemplateElement(PwmSettingTemplate pwmSettingTemplate) {
+        final Element element = PwmSettingXml.readTemplateXml(pwmSettingTemplate);
+        if (element == null) {
+            throw new IllegalStateException("missing PwmSetting.xml template element for " + pwmSettingTemplate);
+        }
+        return element;
+    }
+
+    public static List<PwmSettingTemplate> sortedValues(final Locale locale) {
+        final Map<String,PwmSettingTemplate> sortedValues = new TreeMap<>();
+
+        for (final PwmSettingTemplate pwmSettingTemplate : values()) {
+            sortedValues.put(pwmSettingTemplate.getLabel(locale), pwmSettingTemplate);
+        }
+
+        return Collections.unmodifiableList(new ArrayList<>(sortedValues.values()));
+    }
+}

+ 1 - 2
pwm/servlet/src/password/pwm/config/PwmSettingXml.java

@@ -42,7 +42,6 @@ public class PwmSettingXml {
            "." + PwmSetting.class.getSimpleName()).replace(".","/") + ".xml";
 
     private static Document xmlDocCache = null;
-    //private static int counter;
 
     private static Document readXml() {
         //new Exception().printStackTrace();
@@ -86,7 +85,7 @@ public class PwmSettingXml {
         return (Element)xp.evaluateFirst(readXml());
     }
 
-    static Element readTemplateXml(final PwmSetting.Template template) {
+    static Element readTemplateXml(final PwmSettingTemplate template) {
         final XPathFactory xpfac = XPathFactory.instance();
         final XPathExpression xp = xpfac.compile("/settings/template[@key=\"" + template.toString() + "\"]");
         return (Element)xp.evaluateFirst(readXml());

+ 5 - 5
pwm/servlet/src/password/pwm/config/SettingUIFunction.java

@@ -22,13 +22,13 @@
 
 package password.pwm.config;
 
-import password.pwm.PwmApplication;
-import password.pwm.http.PwmSession;
+import password.pwm.http.PwmRequest;
+
+import java.io.Serializable;
 
 public interface SettingUIFunction {
-    String provideFunction(
-            final PwmApplication pwmApplication,
-            final PwmSession pwmSession,
+    Serializable provideFunction(
+            final PwmRequest pwmRequest,
             final StoredConfiguration storedConfiguration,
             final PwmSetting setting,
             final String profile

+ 34 - 13
pwm/servlet/src/password/pwm/config/StoredConfiguration.java

@@ -358,7 +358,7 @@ public class StoredConfiguration implements Serializable {
         }
     }
 
-    private static StoredValue defaultValue(final PwmSetting pwmSetting, final PwmSetting.Template template)
+    private static StoredValue defaultValue(final PwmSetting pwmSetting, final PwmSettingTemplate template)
     {
         try {
             return pwmSetting.getDefaultValue(template);
@@ -369,18 +369,18 @@ public class StoredConfiguration implements Serializable {
         }
     }
 
-    public PwmSetting.Template getTemplate() {
+    public PwmSettingTemplate getTemplate() {
         final String propertyValue = readConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE);
         try {
-            return PwmSetting.Template.valueOf(propertyValue);
+            return PwmSettingTemplate.valueOf(propertyValue);
         } catch (IllegalArgumentException e) {
-            return PwmSetting.Template.DEFAULT;
+            return PwmSettingTemplate.DEFAULT;
         } catch (NullPointerException e) {
-            return PwmSetting.Template.DEFAULT;
+            return PwmSettingTemplate.DEFAULT;
         }
     }
 
-    public void setTemplate(PwmSetting.Template template) {
+    public void setTemplate(PwmSettingTemplate template) {
         writeConfigProperty(ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
     }
 
@@ -579,7 +579,7 @@ public class StoredConfiguration implements Serializable {
 
         final LinkedHashSet<ConfigRecordID> returnSet = new LinkedHashSet<>();
         boolean firstIter = true;
-        for (final String searchWord : searchTerm.split(" ")) {
+        for (final String searchWord : StringUtil.whitespaceSplit(searchTerm)) { // split on whitespace
             final LinkedHashSet<ConfigRecordID> loopResults = new LinkedHashSet<>();
             for (final PwmSetting loopSetting : PwmSetting.values()) {
                 if (loopSetting.getCategory().hasProfiles()) {
@@ -607,31 +607,38 @@ public class StoredConfiguration implements Serializable {
         return new ArrayList<>(returnSet);
     }
 
-    private boolean matchSetting(final PwmSetting setting, final StoredValue value, final String searchTerm, final Locale locale) {
+    public boolean matchSetting(final PwmSetting setting, final StoredValue value, final String searchTerm, final Locale locale) {
         if (setting.isHidden() || setting.getCategory().isHidden()) {
             return false;
         }
+
+        if (searchTerm == null || searchTerm.isEmpty()) {
+            return false;
+        }
+
+        final String lowerSearchTerm = searchTerm.toLowerCase();
+
         {
             final String key = setting.getKey();
-            if (key.toLowerCase().contains(searchTerm.toLowerCase())) {
+            if (key.toLowerCase().contains(lowerSearchTerm)) {
                 return true;
             }
         }
         {
             final String label = setting.getLabel(locale);
-            if (label.toLowerCase().contains(searchTerm.toLowerCase())) {
+            if (label.toLowerCase().contains(lowerSearchTerm)) {
                 return true;
             }
         }
         {
             final String descr = setting.getDescription(locale);
-            if (descr.toLowerCase().contains(searchTerm.toLowerCase())) {
+            if (descr.toLowerCase().contains(lowerSearchTerm)) {
                 return true;
             }
         }
         {
             final String menuLocationString = setting.toMenuLocationDebug(null,locale);
-            if (menuLocationString.toLowerCase().contains(searchTerm.toLowerCase())) {
+            if (menuLocationString.toLowerCase().contains(lowerSearchTerm)) {
                 return true;
             }
         }
@@ -641,10 +648,24 @@ public class StoredConfiguration implements Serializable {
         }
         {
             final String valueDebug = value.toDebugString(true, locale);
-            if (valueDebug.toLowerCase().contains(searchTerm.toLowerCase())) {
+            if (valueDebug.toLowerCase().contains(lowerSearchTerm)) {
                 return true;
             }
         }
+        if (PwmSettingSyntax.SELECT == setting.getSyntax()
+                || PwmSettingSyntax.OPTIONLIST == setting.getSyntax()
+                || PwmSettingSyntax.VERIFICATION_METHOD == setting.getSyntax()
+                ) {
+            for (final String key : setting.getOptions().keySet()) {
+                if (key.toLowerCase().contains(lowerSearchTerm)) {
+                    return true;
+                }
+                final String optionValue = setting.getOptions().get(key);
+                if (optionValue != null && optionValue.toLowerCase().contains(lowerSearchTerm)) {
+                    return true;
+                }
+            }
+        }
         return false;
     }
 

+ 72 - 0
pwm/servlet/src/password/pwm/config/function/AbstractUriCertImportFunction.java

@@ -0,0 +1,72 @@
+package password.pwm.config.function;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.SettingUIFunction;
+import password.pwm.config.StoredConfiguration;
+import password.pwm.config.value.X509CertificateValue;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmSession;
+import password.pwm.util.X509Utils;
+
+import java.net.URI;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+abstract class AbstractUriCertImportFunction implements SettingUIFunction {
+
+    @Override
+    public String provideFunction(
+            PwmRequest pwmRequest,
+            StoredConfiguration storedConfiguration,
+            PwmSetting setting,
+            String profile
+    )
+            throws PwmOperationalException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
+
+        final String naafUrl = (String)storedConfiguration.readSetting(getSetting()).toNativeObject();
+        if (naafUrl != null && !naafUrl.isEmpty()) {
+            try {
+                final X509Certificate[] certs = X509Utils.readRemoteCertificates(URI.create(naafUrl));
+                if (certs != null) {
+                    resultCertificates.addAll(Arrays.asList(certs));
+                }
+            } catch (Exception e) {
+                if (e instanceof PwmException) {
+                    throw new PwmOperationalException(((PwmException) e).getErrorInformation());
+                }
+                ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"error importing certificates: " + e.getMessage());
+                throw new PwmOperationalException(errorInformation);
+            }
+        } else {
+            ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,"Setting " + getSetting().toMenuLocationDebug(null, null) + " must first be configured");
+            throw new PwmOperationalException(errorInformation);
+        }
+
+        final UserIdentity userIdentity = pwmSession.getSessionStateBean().isAuthenticated() ? pwmSession.getUserInfoBean().getUserIdentity() : null;
+        storedConfiguration.writeSetting(setting, new X509CertificateValue(resultCertificates), userIdentity);
+
+        final StringBuffer returnStr = new StringBuffer();
+        for (final X509Certificate loopCert : resultCertificates) {
+            returnStr.append(X509Utils.makeDebugText(loopCert));
+            returnStr.append("\n\n");
+        }
+        return returnStr.toString();
+        //return Message.getLocalizedMessage(pwmSession.getSessionStateBean().getLocale(), Message.Success_Unknown, pwmApplication.getConfig());
+    }
+
+    abstract PwmSetting getSetting();
+
+
+}

+ 6 - 3
pwm/servlet/src/password/pwm/config/function/LdapCertImportFunction.java

@@ -33,6 +33,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
+import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.i18n.Message;
 import password.pwm.util.X509Utils;
@@ -48,14 +49,16 @@ public class LdapCertImportFunction implements SettingUIFunction {
 
     @Override
     public String provideFunction(
-            PwmApplication pwmApplication,
-            PwmSession pwmSession,
+            PwmRequest pwmRequest,
             StoredConfiguration storedConfiguration,
             PwmSetting setting,
             String profile
     )
             throws PwmOperationalException
     {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+
         final StringArrayValue ldapUrlsValue = (StringArrayValue)storedConfiguration.readSetting(PwmSetting.LDAP_SERVER_URLS,profile);
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
         try {
@@ -63,7 +66,7 @@ public class LdapCertImportFunction implements SettingUIFunction {
                 final List<String> ldapUrlStrings = ldapUrlsValue.toNativeObject();
                 for (final String ldapUrlString : ldapUrlStrings) {
                     final URI ldapURI = new URI(ldapUrlString);
-                    final X509Certificate[] certs = X509Utils.readLdapServerCerts(ldapURI);
+                    final X509Certificate[] certs = X509Utils.readRemoteCertificates(ldapURI);
                     if (certs != null) {
                         resultCertificates.addAll(Arrays.asList(certs));
                     }

+ 11 - 0
pwm/servlet/src/password/pwm/config/function/NAAFCertImportFunction.java

@@ -0,0 +1,11 @@
+package password.pwm.config.function;
+
+import password.pwm.config.PwmSetting;
+
+public class NAAFCertImportFunction extends AbstractUriCertImportFunction {
+
+    @Override
+    PwmSetting getSetting() {
+        return PwmSetting.NAAF_WS_URL;
+    }
+}

+ 11 - 0
pwm/servlet/src/password/pwm/config/function/OAuthCertImportFunction.java

@@ -0,0 +1,11 @@
+package password.pwm.config.function;
+
+import password.pwm.config.PwmSetting;
+
+public class OAuthCertImportFunction extends AbstractUriCertImportFunction {
+
+    @Override
+    PwmSetting getSetting() {
+        return PwmSetting.OAUTH_ID_CODERESOLVE_URL;
+    }
+}

+ 6 - 3
pwm/servlet/src/password/pwm/config/function/SyslogCertImportFunction.java

@@ -33,6 +33,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.event.SyslogAuditService;
+import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.i18n.Message;
 import password.pwm.util.X509Utils;
@@ -46,14 +47,16 @@ public class SyslogCertImportFunction implements SettingUIFunction {
 
     @Override
     public String provideFunction(
-            PwmApplication pwmApplication,
-            PwmSession pwmSession,
+            PwmRequest pwmRequest,
             StoredConfiguration storedConfiguration,
             PwmSetting setting,
             String profile
     )
             throws PwmOperationalException
     {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+
         final Set<X509Certificate> resultCertificates = new LinkedHashSet<>();
 
         final String syslogConfigStr = (String)storedConfiguration.readSetting(PwmSetting.AUDIT_SYSLOG_SERVERS).toNativeObject();
@@ -61,7 +64,7 @@ public class SyslogCertImportFunction implements SettingUIFunction {
             final SyslogAuditService.SyslogConfig syslogConfig = SyslogAuditService.SyslogConfig.fromConfigString(syslogConfigStr);
             if (syslogConfig != null) {
                 try {
-                    final X509Certificate[] certs = X509Utils.readLdapServerCerts(syslogConfig.getHost(), syslogConfig.getPort());
+                    final X509Certificate[] certs = X509Utils.readRemoteCertificates(syslogConfig.getHost(), syslogConfig.getPort());
                     if (certs != null) {
                         resultCertificates.addAll(Arrays.asList(certs));
                     }

+ 16 - 15
pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java

@@ -34,36 +34,38 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.PwmSession;
+import password.pwm.http.PwmRequest;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.util.logging.PwmLogger;
 
+import java.io.Serializable;
 import java.util.*;
 
 public class UserMatchViewerFunction implements SettingUIFunction {
     private static final PwmLogger LOGGER = PwmLogger.forClass(UserMatchViewerFunction.class);
 
     @Override
-    public String provideFunction(
-            final PwmApplication pwmApplication,
-            final PwmSession pwmSession,
+    public Serializable provideFunction(
+            PwmRequest pwmRequest,
             final StoredConfiguration storedConfiguration,
             final PwmSetting setting,
             final String profile
     )
             throws Exception
     {
-        final Locale userLocale = pwmSession == null ? PwmConstants.DEFAULT_LOCALE : pwmSession.getSessionStateBean().getLocale();
-        final int maxResultSize = Integer.parseInt(
-                pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT));
-        final Map<String,List<String>> matchingUsers = discoverMatchingUsers(pwmApplication, maxResultSize, storedConfiguration, setting, profile);
-        return convertResultsToHtmlTable(
-                pwmApplication, userLocale, matchingUsers, maxResultSize
-        );
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+
+        final int maxResultSize = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT));
+        final Collection<UserIdentity> users = discoverMatchingUsers(pwmApplication, maxResultSize, storedConfiguration, setting, profile);
+
+        final HashMap<String,Object> output = new HashMap<>();
+        output.put("users", users);
+        output.put("sizeExceeded", users.size() >= maxResultSize);
+        return output;
     }
 
-    public Map<String,List<String>> discoverMatchingUsers(
+    public Collection<UserIdentity> discoverMatchingUsers(
             final PwmApplication pwmApplication,
             final int maxResultSize,
             final StoredConfiguration storedConfiguration,
@@ -74,7 +76,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
     {
         final Configuration config = new Configuration(storedConfiguration);
         final PwmApplication tempApplication = new PwmApplication.PwmEnvironment(config,pwmApplication.getApplicationPath())
-                .setApplicationMode(PwmApplication.MODE.CONFIGURATION)
+                .setApplicationMode(PwmApplication.MODE.NEW)
                 .setInitLogging(false)
                 .setConfigurationFile(null)
                 .setWebInfPath(pwmApplication.getWebInfPath())
@@ -91,8 +93,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
             }
         }
 
-        final Map<UserIdentity, Map<String, String>> results = LdapPermissionTester.discoverMatchingUsers(tempApplication, maxResultSize, permissions);
-        return sortResults(results);
+        return LdapPermissionTester.discoverMatchingUsers(tempApplication, maxResultSize, permissions).keySet();
     }
 
     public String convertResultsToHtmlTable(

+ 19 - 15
pwm/servlet/src/password/pwm/config/option/RecoveryVerificationMethod.java → pwm/servlet/src/password/pwm/config/option/RecoveryVerificationMethods.java

@@ -23,29 +23,30 @@
 package password.pwm.config.option;
 
 import password.pwm.config.Configuration;
-import password.pwm.i18n.Config;
 import password.pwm.i18n.Display;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
-public enum RecoveryVerificationMethod implements ConfigurationOption {
-    PREVIOUS_AUTH(      false,  Display.Field_VerificationMethodPreviousAuth,       Config.Field_VerificationMethodPreviousAuth),
-    ATTRIBUTES(         true,   Display.Field_VerificationMethodAttributes,         Config.Field_VerificationMethodAttributes),
-    CHALLENGE_RESPONSES(true,   Display.Field_VerificationMethodChallengeResponses, Config.Field_VerificationMethodChallengeResponses),
-    TOKEN(              true,   Display.Field_VerificationMethodToken,              Config.Field_VerificationMethodToken),
-    OTP(                true,   Display.Field_VerificationMethodOTP,                Config.Field_VerificationMethodOTP),
-    REMOTE_RESPONSES(   false,  Display.Field_VerificationMethodRemoteResponses,    Config.Field_VerificationMethodRemoteResponses),
+public enum RecoveryVerificationMethods implements ConfigurationOption {
+    PREVIOUS_AUTH(      false,  Display.Field_VerificationMethodPreviousAuth),
+    ATTRIBUTES(         true,   Display.Field_VerificationMethodAttributes),
+    CHALLENGE_RESPONSES(true,   Display.Field_VerificationMethodChallengeResponses),
+    TOKEN(              true,   Display.Field_VerificationMethodToken),
+    OTP(                true,   Display.Field_VerificationMethodOTP),
+    REMOTE_RESPONSES(   false,  Display.Field_VerificationMethodRemoteResponses),
+    NAAF(               true,   Display.Field_VerificationMethodNAAF),
 
     ;
     
     private final boolean userSelectable;
     private final Display displayKey;
-    private final Config configDisplayKey;
 
-    RecoveryVerificationMethod(boolean userSelectable, Display displayKey, Config configDisplayKey) {
+    RecoveryVerificationMethods(boolean userSelectable, Display displayKey) {
         this.userSelectable = userSelectable;
         this.displayKey = displayKey;
-        this.configDisplayKey = configDisplayKey;
     }
 
     public boolean isUserSelectable() {
@@ -56,11 +57,14 @@ public enum RecoveryVerificationMethod implements ConfigurationOption {
         return displayKey;
     }
 
-    public Config getConfigDisplayKey() {
-        return configDisplayKey;
-    }
-
     public String getLabel(final Configuration configuration, final Locale locale) {
         return Display.getLocalizedMessage(locale, this.getDisplayKey(), configuration);
     }
+
+    public static RecoveryVerificationMethods[] availableValues() {
+        final List<RecoveryVerificationMethods> values = new ArrayList<>();
+        values.addAll(Arrays.asList(RecoveryVerificationMethods.values()));
+        return values.toArray(new RecoveryVerificationMethods[values.size()]);
+    }
+
 }

+ 11 - 11
pwm/servlet/src/password/pwm/config/profile/ForgottenPasswordProfile.java

@@ -26,15 +26,15 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.StoredConfiguration;
 import password.pwm.config.StoredValue;
-import password.pwm.config.option.RecoveryVerificationMethod;
+import password.pwm.config.option.RecoveryVerificationMethods;
 import password.pwm.config.value.VerificationMethodValue;
 
 import java.util.*;
 
 public class ForgottenPasswordProfile extends AbstractProfile {
 
-    private Set<RecoveryVerificationMethod> requiredRecoveryVerificationMethods;
-    private Set<RecoveryVerificationMethod> optionalRecoveryVerificationMethods;
+    private Set<RecoveryVerificationMethods> requiredRecoveryVerificationMethods;
+    private Set<RecoveryVerificationMethods> optionalRecoveryVerificationMethods;
 
     public ForgottenPasswordProfile(String identifier, Map<PwmSetting, StoredValue> storedValueMap) {
         super(identifier, storedValueMap);
@@ -61,29 +61,29 @@ public class ForgottenPasswordProfile extends AbstractProfile {
         return ProfileType.ForgottenPassword;
     }
     
-    public Set<RecoveryVerificationMethod> requiredRecoveryAuthenticationMethods() {
+    public Set<RecoveryVerificationMethods> requiredRecoveryAuthenticationMethods() {
         if (requiredRecoveryVerificationMethods == null) {
             requiredRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.required);
         }
         return requiredRecoveryVerificationMethods;
     }
 
-    public Set<RecoveryVerificationMethod> optionalRecoveryAuthenticationMethods() {
+    public Set<RecoveryVerificationMethods> optionalRecoveryAuthenticationMethods() {
         if (optionalRecoveryVerificationMethods == null) {
             optionalRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.optional);
         }
         return optionalRecoveryVerificationMethods;
     }
     
-    private Set<RecoveryVerificationMethod> readRecoveryAuthMethods(final VerificationMethodValue.EnabledState enabledState) {
-        final Set<RecoveryVerificationMethod> result = new LinkedHashSet<>();
+    private Set<RecoveryVerificationMethods> readRecoveryAuthMethods(final VerificationMethodValue.EnabledState enabledState) {
+        final Set<RecoveryVerificationMethods> result = new LinkedHashSet<>();
         final StoredValue configValue = storedValueMap.get(PwmSetting.RECOVERY_VERIFICATION_METHODS);
         final VerificationMethodValue.VerificationMethodSettings verificationMethodSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
 
-        for (final RecoveryVerificationMethod recoveryVerificationMethod : RecoveryVerificationMethod.values()) {
-            if (verificationMethodSettings.getMethodSettings().containsKey(recoveryVerificationMethod)) {
-                if (verificationMethodSettings.getMethodSettings().get(recoveryVerificationMethod).getEnabledState() == enabledState) {
-                    result.add(recoveryVerificationMethod);
+        for (final RecoveryVerificationMethods recoveryVerificationMethods : RecoveryVerificationMethods.availableValues()) {
+            if (verificationMethodSettings.getMethodSettings().containsKey(recoveryVerificationMethods)) {
+                if (verificationMethodSettings.getMethodSettings().get(recoveryVerificationMethods).getEnabledState() == enabledState) {
+                    result.add(recoveryVerificationMethods);
                 }
             }
         }

+ 1 - 1
pwm/servlet/src/password/pwm/config/value/PasswordValue.java

@@ -159,7 +159,7 @@ public class PasswordValue implements StoredValue {
             throws PwmUnrecoverableException, UnsupportedEncodingException, NoSuchAlgorithmException
     {
         final SecretKey secretKey = SecureHelper.makeKey(key);
-        return SecureHelper.encryptToString(value, secretKey);
+        return SecureHelper.encryptToString(value, secretKey, false, SecureHelper.BlockAlgorithm.CONFIG);
     }
 
     public boolean requiresStoredUpdate()

+ 25 - 11
pwm/servlet/src/password/pwm/config/value/VerificationMethodValue.java

@@ -4,16 +4,13 @@ import org.jdom2.CDATA;
 import org.jdom2.Element;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.StoredValue;
-import password.pwm.config.option.RecoveryVerificationMethod;
+import password.pwm.config.option.RecoveryVerificationMethods;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.Serializable;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class VerificationMethodValue extends AbstractValue implements StoredValue {
     private static final PwmLogger LOGGER = PwmLogger.forClass(VerificationMethodValue.class);
@@ -28,18 +25,18 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
     }
 
     public static class VerificationMethodSettings implements Serializable {
-        private Map<RecoveryVerificationMethod,VerificationMethodSetting> methodSettings = new HashMap<>();
+        private Map<RecoveryVerificationMethods,VerificationMethodSetting> methodSettings = new HashMap<>();
         private int minOptionalRequired = 0;
 
         public VerificationMethodSettings() {
         }
 
-        public VerificationMethodSettings(Map<RecoveryVerificationMethod, VerificationMethodSetting> methodSettings, int minOptionalRequired) {
+        public VerificationMethodSettings(Map<RecoveryVerificationMethods, VerificationMethodSetting> methodSettings, int minOptionalRequired) {
             this.methodSettings = methodSettings;
             this.minOptionalRequired = minOptionalRequired;
         }
 
-        public Map<RecoveryVerificationMethod, VerificationMethodSetting> getMethodSettings() {
+        public Map<RecoveryVerificationMethods, VerificationMethodSetting> getMethodSettings() {
             return Collections.unmodifiableMap(methodSettings);
         }
 
@@ -66,9 +63,9 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
 
     public VerificationMethodValue(VerificationMethodSettings value) {
         this.value = value;
-        for (final RecoveryVerificationMethod recoveryVerificationMethod : RecoveryVerificationMethod.values()) {
-            if (!value.methodSettings.containsKey(recoveryVerificationMethod)) {
-                value.methodSettings.put(recoveryVerificationMethod,new VerificationMethodSetting(EnabledState.disabled));
+        for (final RecoveryVerificationMethods recoveryVerificationMethods : RecoveryVerificationMethods.availableValues()) {
+            if (!value.methodSettings.containsKey(recoveryVerificationMethods)) {
+                value.methodSettings.put(recoveryVerificationMethods,new VerificationMethodSetting(EnabledState.disabled));
             }
         }
     }
@@ -113,4 +110,21 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
     public List<String> validateValue(PwmSetting pwm) {
         return Collections.emptyList();
     }
+
+    @Override
+    public String toDebugString(boolean prettyFormat, Locale locale) {
+        if (value == null) {
+            return "No Verification Methods";
+        }
+        if (!prettyFormat) {
+            return super.toDebugString(prettyFormat, locale);
+        }
+        final StringBuilder out = new StringBuilder();
+        for (final RecoveryVerificationMethods method : value.getMethodSettings().keySet()) {
+            out.append(" ").append(method.toString()).append(": ").append(value.getMethodSettings().get(method).getEnabledState());
+            out.append("\n");
+        }
+        out.append("  Minimum Optional Methods Required: " + value.getMinOptionalRequired());
+        return out.toString();
+    }
 }

+ 4 - 0
pwm/servlet/src/password/pwm/error/ErrorInformation.java

@@ -143,6 +143,10 @@ public class ErrorInformation implements Serializable {
             return userStrOverride;
         }
 
+        if (this.getError().getErrorCode() == 6000) {
+            return detailedErrorMsg;
+        }
+
         return this.getError().getLocalizedMessage(userLocale, config, fieldValues);
     }
 

+ 3 - 1
pwm/servlet/src/password/pwm/error/PwmError.java

@@ -101,7 +101,7 @@ public enum PwmError {
     ERROR_INTRUDER_ADDRESS("Error_AddressIntruder", 5024, true),
     ERROR_INTRUDER_SESSION("Error_SessionIntruder", 5025, true),
     ERROR_BAD_SESSION_PASSWORD("Error_BadSessionPassword", 5026, false),
-    ERROR_UNAUTHORIZED("Error_Unauthorized", 5027, false),
+    ERROR_UNAUTHORIZED("Error_Unauthorized", 5027, true),
     ERROR_BAD_SESSION("Error_BadSession", 5028, false),
     ERROR_MISSING_REQUIRED_RESPONSE("Error_MissingRequiredResponse", 5029, false),
     ERROR_MISSING_RANDOM_RESPONSE("Error_MissingRandomResponse", 5030, false),
@@ -160,6 +160,8 @@ public enum PwmError {
     ERROR_NO_PROFILE_ASSIGNED("Error_NoProfileAssigned",5081,true),
     ERROR_STARTUP_ERROR("Error_StartupError",5082,true),
 
+    ERROR_REMOTE_ERROR_VALUE("Error_RemoteErrorValue",6000,true),
+
     ERROR_FIELD_REQUIRED("Error_FieldRequired", 5100, false),
     ERROR_FIELD_NOT_A_NUMBER("Error_FieldNotANumber", 5101, false),
     ERROR_FIELD_INVALID_EMAIL("Error_FieldInvalidEmail", 5102, false),

+ 1 - 1
pwm/servlet/src/password/pwm/event/SyslogAuditService.java

@@ -316,7 +316,7 @@ public class SyslogAuditService {
             if (certificates != null && certificates.length >= 1) {
                 try {
                     final SSLContext sc = SSLContext.getInstance("SSL");
-                    sc.init(null, new X509TrustManager[]{new X509Utils.PwmTrustManager(certificates)},
+                    sc.init(null, new X509TrustManager[]{new X509Utils.CertMatchingTrustManager(configuration, certificates)},
                             new java.security.SecureRandom());
                     return sc.getSocketFactory();
                 } catch (NoSuchAlgorithmException | KeyManagementException e) {

+ 10 - 4
pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java

@@ -45,7 +45,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserStatusReader;
-import password.pwm.util.JsonUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.TimeDuration;
@@ -465,12 +464,19 @@ public class LDAPStatusChecker implements HealthChecker {
         final Set<ChaiProvider.DIRECTORY_VENDOR> discoveredVendors = new HashSet<>(replicaVendorMap.values());
 
         if (discoveredVendors.size() >= 2) {
-            final String mapAsJson = JsonUtil.serializeMap(replicaVendorMap);
-            healthRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_VendorsNotSame, mapAsJson));
+            final StringBuilder vendorMsg = new StringBuilder();
+            for (final Iterator<String> iterator = replicaVendorMap.keySet().iterator(); iterator.hasNext(); ) {
+                final String key = iterator.next();
+                vendorMsg.append(key).append("=").append(replicaVendorMap.get(key).toString());
+                if (iterator.hasNext()) {
+                    vendorMsg.append(", ");
+                }
+            }
+            healthRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_VendorsNotSame, vendorMsg.toString()));
             // cache the error
             healthProperties.put(HealthMonitor.HealthProperty.LdapVendorSameCheck, healthRecords);
 
-            LOGGER.warn(PwmConstants.HEALTH_SESSION_LABEL,"multiple ldap vendors found: " + mapAsJson);
+            LOGGER.warn(PwmConstants.HEALTH_SESSION_LABEL,"multiple ldap vendors found: " + vendorMsg.toString());
         } else if (discoveredVendors.size() == 1) {
             if (!errorReachingServer) {
                 // cache the no errors

+ 35 - 0
pwm/servlet/src/password/pwm/http/PwmHttpRequestWrapper.java

@@ -130,6 +130,41 @@ public abstract class PwmHttpRequestWrapper {
         return Collections.unmodifiableMap(outputMap);
     }
 
+    public Map<String, Object> readBodyAsJsonMap(boolean bypassInputValidation)
+            throws IOException, PwmUnrecoverableException
+    {
+        final String bodyString = readRequestBodyAsString();
+        final Map<String, Object> inputMap = JsonUtil.deserializeMap(bodyString);
+
+        final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
+        final boolean passwordTrim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_PASSWORD_TRIM));
+        final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
+
+        final Map<String, Object> outputMap = new LinkedHashMap<>();
+        if (inputMap != null) {
+            for (final String key : inputMap.keySet()) {
+                if (key != null) {
+                    final boolean passwordType = key.toLowerCase().contains("password");
+                    final Object value;
+                    if (inputMap.get(key) instanceof String) {
+                        String stringValue = bypassInputValidation
+                                ? (String)inputMap.get(key) :
+                                Validator.sanitizeInputValue(configuration, (String)inputMap.get(key), maxLength);
+                        stringValue = passwordType && passwordTrim ? stringValue.trim() : stringValue;
+                        stringValue = !passwordType && trim ? stringValue.trim() : stringValue;
+                        value = stringValue;
+                    } else {
+                        value = inputMap.get(key);
+                    }
+
+                    final String sanitizedName = Validator.sanitizeInputValue(configuration, key, maxLength);
+                    outputMap.put(sanitizedName, value);
+                }
+            }
+        }
+
+        return Collections.unmodifiableMap(outputMap);
+    }
 
     public PasswordData readParameterAsPassword(final String name)
             throws PwmUnrecoverableException 

+ 1 - 0
pwm/servlet/src/password/pwm/http/PwmRequest.java

@@ -81,6 +81,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         HIDE_HEADER_WARNINGS,
         NO_REQ_COUNTER,
         NO_IDLE_TIMEOUT,
+        NO_MOBILE_CSS,
         ALWAYS_EXPAND_MESSAGE_TEXT,
     }
 

+ 1 - 1
pwm/servlet/src/password/pwm/http/PwmResponse.java

@@ -116,7 +116,7 @@ public class PwmResponse extends PwmHttpResponseWrapper {
         final String outputString = restResultBean.toJson();
         resp.setContentType(PwmConstants.ContentTypeValue.json.getHeaderValue());
         resp.getWriter().print(outputString);
-        resp.getWriter().flush();
+        resp.getWriter().close();
     }
 
     public void forwardToLoginPage()

+ 1 - 1
pwm/servlet/src/password/pwm/http/PwmSession.java

@@ -337,7 +337,7 @@ public class PwmSession implements Serializable {
     public void setSessionTimeout(final HttpSession session, final int maxSeconds)
             throws PwmUnrecoverableException
     {
-        if (session.getMaxInactiveInterval() < maxSeconds) {
+        if (maxSeconds > 0) {
             session.setMaxInactiveInterval(maxSeconds);
         }
     }

+ 4 - 4
pwm/servlet/src/password/pwm/http/bean/ConfigGuideBean.java

@@ -22,7 +22,7 @@
 
 package password.pwm.http.bean;
 
-import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredConfiguration;
 import password.pwm.http.servlet.ConfigGuideServlet;
 
@@ -34,7 +34,7 @@ public class ConfigGuideBean implements PwmSessionBean {
 
     private ConfigGuideServlet.STEP step = ConfigGuideServlet.STEP.START;
     private StoredConfiguration storedConfiguration = StoredConfiguration.newStoredConfiguration();
-    private PwmSetting.Template selectedTemplate = null;
+    private PwmSettingTemplate selectedTemplate = null;
     private Map<String,String> formData = new HashMap<>();
     private X509Certificate[] ldapCertificates;
     private boolean certsTrustedbyKeystore = false;
@@ -92,12 +92,12 @@ public class ConfigGuideBean implements PwmSessionBean {
         this.useConfiguredCerts = useConfiguredCerts;
     }
 
-    public PwmSetting.Template getSelectedTemplate()
+    public PwmSettingTemplate getSelectedTemplate()
     {
         return selectedTemplate;
     }
 
-    public void setSelectedTemplate(PwmSetting.Template selectedTemplate)
+    public void setSelectedTemplate(PwmSettingTemplate selectedTemplate)
     {
         this.selectedTemplate = selectedTemplate;
     }

+ 23 - 12
pwm/servlet/src/password/pwm/http/bean/ForgottenPasswordBean.java

@@ -24,10 +24,11 @@ package password.pwm.http.bean;
 
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ResponseSet;
+import password.pwm.RecoveryVerificationMethod;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.option.MessageSendMethod;
-import password.pwm.config.option.RecoveryVerificationMethod;
+import password.pwm.config.option.RecoveryVerificationMethods;
 
 import java.io.Serializable;
 import java.util.*;
@@ -121,13 +122,15 @@ public class ForgottenPasswordBean implements PwmSessionBean {
     public static class Progress implements Serializable {
         private boolean tokenSent;
         private boolean allPassed;
-        private final Set<RecoveryVerificationMethod> satisfiedMethods = new HashSet<>();
+        private final Set<RecoveryVerificationMethods> satisfiedMethods = new HashSet<>();
 
         private MessageSendMethod tokenSendChoice;
         private String tokenSentAddress;
-        private RecoveryVerificationMethod inProgressVerificationMethod;
+        private RecoveryVerificationMethods inProgressVerificationMethod;
 
-        public Set<RecoveryVerificationMethod> getSatisfiedMethods() {
+        private transient RecoveryVerificationMethod naafRecoveryMethod;
+
+        public Set<RecoveryVerificationMethods> getSatisfiedMethods() {
             return satisfiedMethods;
         }
 
@@ -171,19 +174,27 @@ public class ForgottenPasswordBean implements PwmSessionBean {
             this.tokenSentAddress = tokenSentAddress;
         }
 
-        public RecoveryVerificationMethod getInProgressVerificationMethod() {
+        public RecoveryVerificationMethods getInProgressVerificationMethod() {
             return inProgressVerificationMethod;
         }
 
-        public void setInProgressVerificationMethod(RecoveryVerificationMethod inProgressVerificationMethod) {
+        public void setInProgressVerificationMethod(RecoveryVerificationMethods inProgressVerificationMethod) {
             this.inProgressVerificationMethod = inProgressVerificationMethod;
         }
+
+        public void setNaafRecoveryMethod(RecoveryVerificationMethod naafRecoveryMethod) {
+            this.naafRecoveryMethod = naafRecoveryMethod;
+        }
+
+        public RecoveryVerificationMethod getNaafRecoveryMethod() {
+            return naafRecoveryMethod;
+        }
     }
 
     public static class RecoveryFlags implements Serializable {
         private final boolean allowWhenLdapIntruderLocked;
-        private final Set<RecoveryVerificationMethod> requiredAuthMethods;
-        private final Set<RecoveryVerificationMethod> optionalAuthMethods;
+        private final Set<RecoveryVerificationMethods> requiredAuthMethods;
+        private final Set<RecoveryVerificationMethods> optionalAuthMethods;
         private final int minimumOptionalAuthMethods;
         private final MessageSendMethod tokenSendMethod;
 
@@ -197,8 +208,8 @@ public class ForgottenPasswordBean implements PwmSessionBean {
         }
 
         public RecoveryFlags(
-                final Set<RecoveryVerificationMethod> requiredAuthMethods,
-                final Set<RecoveryVerificationMethod> optionalAuthMethods,
+                final Set<RecoveryVerificationMethods> requiredAuthMethods,
+                final Set<RecoveryVerificationMethods> optionalAuthMethods,
                 final int minimumOptionalAuthMethods,
                 final boolean allowWhenLdapIntruderLocked,
                 final MessageSendMethod tokenSendMethod
@@ -211,7 +222,7 @@ public class ForgottenPasswordBean implements PwmSessionBean {
             this.tokenSendMethod = tokenSendMethod;
         }
 
-        public Set<RecoveryVerificationMethod> getRequiredAuthMethods() {
+        public Set<RecoveryVerificationMethods> getRequiredAuthMethods() {
             return requiredAuthMethods;
         }
 
@@ -224,7 +235,7 @@ public class ForgottenPasswordBean implements PwmSessionBean {
             return tokenSendMethod;
         }
 
-        public Set<RecoveryVerificationMethod> getOptionalAuthMethods() {
+        public Set<RecoveryVerificationMethods> getOptionalAuthMethods() {
             return optionalAuthMethods;
         }
 

+ 48 - 32
pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java

@@ -18,28 +18,28 @@ import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.impl.conn.SingleClientConnManager;
 import org.apache.http.params.HttpProtocolParams;
 import org.apache.http.util.EntityUtils;
+import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.PwmSession;
 import password.pwm.util.TimeDuration;
+import password.pwm.util.X509Utils;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -50,16 +50,43 @@ public class PwmHttpClient {
     private static int classCounter = 0;
 
     private final PwmApplication pwmApplication;
-    private final PwmSession pwmSession;
+    private final SessionLabel sessionLabel;
+    private final PwmHttpClientConfiguration pwmHttpClientConfiguration;
 
-    public PwmHttpClient(PwmApplication pwmApplication, PwmSession pwmSession) {
+    public PwmHttpClient(PwmApplication pwmApplication, SessionLabel sessionLabel) {
         this.pwmApplication = pwmApplication;
-        this.pwmSession = pwmSession;
+        this.sessionLabel = sessionLabel;
+        this.pwmHttpClientConfiguration = new PwmHttpClientConfiguration(null);
     }
 
-    public static HttpClient getHttpClient(final Configuration configuration) {
-        DefaultHttpClient httpClient;
-            httpClient = new DefaultHttpClient();
+    public PwmHttpClient(PwmApplication pwmApplication, SessionLabel sessionLabel, final PwmHttpClientConfiguration pwmHttpClientConfiguration) {
+        this.pwmApplication = pwmApplication;
+        this.sessionLabel = sessionLabel;
+        this.pwmHttpClientConfiguration = pwmHttpClientConfiguration;
+    }
+
+    public static HttpClient getHttpClient(final Configuration configuration)
+            throws PwmUnrecoverableException
+    {
+        return getHttpClient(configuration, new PwmHttpClientConfiguration(null));
+    }
+
+    public static HttpClient getHttpClient(final Configuration configuration, final PwmHttpClientConfiguration pwmHttpClientConfiguration)
+            throws PwmUnrecoverableException
+    {
+        final DefaultHttpClient httpClient;
+        try {
+            if (Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_HTTP_PROMISCUOUS_ENABLE))) {
+                httpClient = new DefaultHttpClient(makeConnectionManager(new X509Utils.PromiscuousTrustManager()));
+            } else if (pwmHttpClientConfiguration != null && pwmHttpClientConfiguration.getCertificates() != null) {
+                final TrustManager trustManager = new X509Utils.CertMatchingTrustManager(configuration,pwmHttpClientConfiguration.getCertificates());
+                httpClient = new DefaultHttpClient(makeConnectionManager(trustManager));
+            } else {
+                httpClient = new DefaultHttpClient();
+            }
+        } catch (Exception e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unexpected error creating promiscuous https client: " + e.getMessage()));
+        }
         final String strValue = configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL);
         if (strValue != null && strValue.length() > 0) {
             final URI proxyURI = URI.create(strValue);
@@ -111,7 +138,7 @@ public class PwmHttpClient {
     }
 
     public PwmHttpClientResponse makeRequestImpl(final PwmHttpClientRequest clientRequest)
-            throws IOException, URISyntaxException {
+            throws IOException, URISyntaxException, PwmUnrecoverableException {
         final Date startTime = new Date();
         final int counter = classCounter++;
 
@@ -152,8 +179,8 @@ public class PwmHttpClient {
             }
         }
 
-        final HttpClient httpClient = getHttpClient(pwmApplication.getConfig());
-        LOGGER.trace(pwmSession, "preparing to send (id=" + counter + ") " + clientRequest.toDebugString());
+        final HttpClient httpClient = getHttpClient(pwmApplication.getConfig(), pwmHttpClientConfiguration);
+        LOGGER.trace(sessionLabel, "preparing to send (id=" + counter + ") " + clientRequest.toDebugString());
 
         final HttpResponse httpResponse = httpClient.execute(httpRequest);
         final String responseBody = EntityUtils.toString(httpResponse.getEntity());
@@ -172,36 +199,25 @@ public class PwmHttpClient {
         );
 
         final TimeDuration duration = TimeDuration.fromCurrent(startTime);
-        LOGGER.trace(pwmSession, "received response (id=" + counter + ") in " + duration.asCompactString() + ": " + httpClientResponse.toDebugString());
+        LOGGER.trace(sessionLabel, "received response (id=" + counter + ") in " + duration.asCompactString() + ": " + httpClientResponse.toDebugString());
         return httpClientResponse;
     }
 
-    private static ClientConnectionManager ccm()
+    private static ClientConnectionManager makeConnectionManager(TrustManager trustManager)
             throws NoSuchAlgorithmException, KeyManagementException
     {
-        SSLContext sslContext = SSLContext.getInstance("SSL");
-
-        // set up a TrustManager that trusts everything
-        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
-            public X509Certificate[] getAcceptedIssuers() {
-                return new X509Certificate[0];
-            }
+        final SSLContext sslContext = SSLContext.getInstance("SSL");
 
-            public void checkClientTrusted(X509Certificate[] certs, String authType) {
-            }
-
-            public void checkServerTrusted(X509Certificate[] certs, String authType) {
-            }
-        }}, new SecureRandom());
+        sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
 
-        SSLSocketFactory sf = new SSLSocketFactory(sslContext);
-        HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
+        final SSLSocketFactory sf = new SSLSocketFactory(sslContext);
+        final HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
 
-        sf.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);        Scheme httpsScheme = new Scheme("https", 443, sf);
-        SchemeRegistry schemeRegistry = new SchemeRegistry();
+        sf.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
+        final Scheme httpsScheme = new Scheme("https", 443, sf);
+        final SchemeRegistry schemeRegistry = new SchemeRegistry();
         schemeRegistry.register(httpsScheme);
 
-
         return new SingleClientConnManager(schemeRegistry);
     }
 }

+ 15 - 0
pwm/servlet/src/password/pwm/http/client/PwmHttpClientConfiguration.java

@@ -0,0 +1,15 @@
+package password.pwm.http.client;
+
+import java.security.cert.X509Certificate;
+
+public class PwmHttpClientConfiguration {
+    private X509Certificate[] certificates;
+
+    public PwmHttpClientConfiguration(X509Certificate[] certificate) {
+        this.certificates = certificate;
+    }
+
+    public X509Certificate[] getCertificates() {
+        return certificates;
+    }
+}

+ 27 - 2
pwm/servlet/src/password/pwm/http/client/PwmHttpClientRequest.java

@@ -3,6 +3,7 @@ package password.pwm.http.client;
 import password.pwm.http.HttpMethod;
 
 import java.io.Serializable;
+import java.security.cert.X509Certificate;
 import java.util.Collections;
 import java.util.Map;
 
@@ -11,12 +12,33 @@ public class PwmHttpClientRequest implements Serializable {
     private final String url;
     private final String body;
     private final Map<String,String> headers;
+    private final X509Certificate[] trustedCertificates;
 
-    public PwmHttpClientRequest(final HttpMethod method, final String url, final String body, final Map<String, String> headers) {
+    public PwmHttpClientRequest(
+            final HttpMethod method,
+            final String url,
+            final String body,
+            final Map<String, String> headers
+    ) {
         this.method = method;
         this.url = url;
         this.body = body;
         this.headers = headers == null ? Collections.<String,String>emptyMap() : Collections.unmodifiableMap(headers);
+        this.trustedCertificates = null;
+    }
+
+    public PwmHttpClientRequest(
+            final HttpMethod method,
+            final String url,
+            final String body,
+            final Map<String, String> headers,
+            final X509Certificate[] trustedCertificates
+    ) {
+        this.method = method;
+        this.url = url;
+        this.body = body;
+        this.headers = headers == null ? Collections.<String,String>emptyMap() : Collections.unmodifiableMap(headers);
+        this.trustedCertificates = trustedCertificates;
     }
 
     public HttpMethod getMethod() {
@@ -35,8 +57,11 @@ public class PwmHttpClientRequest implements Serializable {
         return headers;
     }
 
+    public X509Certificate[] getTrustedCertificates() {
+        return trustedCertificates;
+    }
+
     public String toDebugString() {
         return PwmHttpClient.entityToDebugString("HTTP " + method + " request to " + url, headers, body);
     }
-
 }

+ 2 - 2
pwm/servlet/src/password/pwm/http/filter/ApplicationModeFilter.java

@@ -110,12 +110,12 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
 
         // block if public request and not running or in trial
         if (!PwmConstants.TRIAL_MODE) {
-            if (pwmURL.isPublicUrl() && !pwmURL.isLogoutURL() && !pwmURL.isCommandServletURL()) {
+            if (pwmURL.isPublicUrl() && !pwmURL.isLogoutURL() && !pwmURL.isCommandServletURL() && !pwmURL.isCaptchaURL())  {
                 if (mode == PwmApplication.MODE.CONFIGURATION) {
                     pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"public services are not available while configuration is open"));
                     return true;
                 }
-                if (pwmRequest.getPwmApplication().getApplicationMode() != PwmApplication.MODE.RUNNING) {
+                if (mode != PwmApplication.MODE.RUNNING) {
                     pwmRequest.respondWithError(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"public services are not available while application is not in running mode"));
                     return true;
                 }

+ 3 - 2
pwm/servlet/src/password/pwm/http/filter/GZIPFilter.java

@@ -64,8 +64,10 @@ public class GZIPFilter implements Filter {
         final String acceptEncoding = ((HttpServletRequest)servletRequest).getHeader(PwmConstants.HttpHeader.Accept_Encoding.getHttpName());
         if (acceptEncoding != null && acceptEncoding.contains("gzip") && isEnabled(servletRequest)) {
             GZIPHttpServletResponseWrapper gzipResponse = new GZIPHttpServletResponseWrapper((HttpServletResponse)servletResponse);
+            gzipResponse.addHeader("Content-Encoding", "gzip");
             filterChain.doFilter(servletRequest, gzipResponse);
             gzipResponse.finish();
+
         } else {
             filterChain.doFilter(servletRequest, servletResponse);
         }
@@ -87,7 +89,7 @@ public class GZIPFilter implements Filter {
             pwmApplication = ContextManager.getPwmApplication((HttpServletRequest) servletRequest);
             return Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_ENABLE_GZIP));
         } catch (PwmUnrecoverableException e) {
-            LOGGER.error("unable to read http-gzip app-property, defaulting to non-gzip: " + e.getMessage());
+            LOGGER.trace("unable to read http-gzip app-property, defaulting to non-gzip: " + e.getMessage());
         }
         return false;
     }
@@ -100,7 +102,6 @@ public class GZIPFilter implements Filter {
 
         public GZIPHttpServletResponseWrapper(HttpServletResponse response) throws IOException {
             super(response);
-            response.addHeader("Content-Encoding", "gzip");
         }
 
         public void finish() throws IOException {

+ 4 - 7
pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java

@@ -559,13 +559,10 @@ public class ActivateUserServlet extends PwmServlet {
         {
             final UserDataReader dataReader = LdapUserDataReader.appProxiedReader(pwmApplication, userIdentity);
             final String toAddress;
-            try {
-                toAddress = dataReader.readStringAttribute(config.readSettingAsString(PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE));
-            } catch (ChaiOperationException e) {
-                final String errorMsg = "unable to read user email attribute due to ldap error, unable to send token: " + e.getMessage();
-                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_ACTIVATION_FAILURE, errorMsg);
-                LOGGER.error(pwmSession.getLabel(), errorInformation);
-                throw new PwmOperationalException(errorInformation);
+            {
+                final EmailItemBean emailItemBean = config.readSettingAsEmail(PwmSetting.EMAIL_ACTIVATION_VERIFICATION, locale);
+                final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
+                toAddress = macroMachine.expandMacros(emailItemBean.getTo());
             }
 
             final String toSmsNumber;

+ 0 - 83
pwm/servlet/src/password/pwm/http/servlet/AdminServlet.java

@@ -26,16 +26,12 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.bean.AboutApplicationBean;
-import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.AdminBean;
-import password.pwm.util.Helper;
-import password.pwm.util.PwmRandom;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.report.ReportService;
 import password.pwm.util.stats.StatisticsManager;
@@ -46,7 +42,6 @@ import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Date;
 
 public class AdminServlet extends PwmServlet {
 
@@ -237,84 +232,6 @@ public class AdminServlet extends PwmServlet {
     }
 
 
-
-    public static AboutApplicationBean makeInfoBean(
-            final PwmApplication pwmApplication
-    ) {
-        final AboutApplicationBean aboutBean = new AboutApplicationBean();
-
-        // about page
-        aboutBean.setVersion(PwmConstants.SERVLET_VERSION);
-        aboutBean.setCurrentTime(new Date());
-        aboutBean.setInstallTime(pwmApplication.getStartupTime());
-        aboutBean.setInstallTime(pwmApplication.getInstallTime());
-        aboutBean.setSiteUrl(pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL));
-        aboutBean.setInstanceID(pwmApplication.getInstanceID());
-        aboutBean.setChaiApiVersion(PwmConstants.CHAI_API_VERSION);
-        if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
-            if (pwmApplication.getVersionChecker() != null) {
-                aboutBean.setCurrentPublishedVersion(pwmApplication.getVersionChecker().currentVersion());
-                aboutBean.setCurrentPublishedVersionCheckTime(pwmApplication.getVersionChecker().lastReadTimestamp());
-            }
-        }
-
-        // localdb info page;
-        aboutBean.setWordlistSize(pwmApplication.getWordlistManager().size());
-        aboutBean.setSeedlistSize(pwmApplication.getSeedlistManager().size());
-        aboutBean.setSharedHistorySize(pwmApplication.getSharedHistoryManager().size());
-        aboutBean.setSharedHistoryOldestTime(pwmApplication.getSharedHistoryManager().getOldestEntryTime());
-
-        aboutBean.setEmailQueueSize(pwmApplication.getEmailQueue().queueSize());
-        aboutBean.setEmailQueueOldestTime(pwmApplication.getEmailQueue().eldestItem());
-
-        aboutBean.setSmsQueueSize(pwmApplication.getSmsQueue().queueSize());
-        aboutBean.setSmsQueueOldestTime(pwmApplication.getSmsQueue().eldestItem());
-
-        aboutBean.setSyslogQueueSize(pwmApplication.getAuditManager().syslogQueueSize());
-        aboutBean.setSyslogQueueOldestTime(null);//@todo
-
-        aboutBean.setLocalDbLogSize(pwmApplication.getLocalDBLogger().getStoredEventCount());
-        aboutBean.setLocalDbLogOldestTime(pwmApplication.getLocalDBLogger().getTailDate());
-
-        aboutBean.setLocalDbStorageSize(Helper.formatDiskSize(Helper.getFileDirectorySize(pwmApplication.getLocalDB().getFileLocation())));
-        aboutBean.setLocalDbFreeSpace(Helper.formatDiskSize(Helper.diskSpaceRemaining(pwmApplication.getLocalDB().getFileLocation())));
-
-        { // java info
-            final Runtime runtime = Runtime.getRuntime();
-            final AboutApplicationBean.JavaInformation javaInformation = new AboutApplicationBean.JavaInformation();
-            javaInformation.setMemoryFree(runtime.freeMemory());
-            javaInformation.setMemoryAllocated(runtime.totalMemory());
-            javaInformation.setMemoryMax(runtime.maxMemory());
-            javaInformation.setThreadCount(Thread.activeCount());
-
-            javaInformation.setVmVendor(System.getProperty("java.vm.vendor"));
-            javaInformation.setRuntimeVersion(System.getProperty("java.runtime.version"));
-            javaInformation.setVmVersion(System.getProperty("java.vm.version"));
-            javaInformation.setVmName(System.getProperty("java.vm.name"));
-            javaInformation.setVmLocation(System.getProperty("java.home"));
-
-            javaInformation.setOsName(System.getProperty("os.name"));
-            javaInformation.setOsVersion(System.getProperty("os.version"));
-            javaInformation.setRandomAlgorithm(PwmRandom.getInstance().getAlgorithm());
-            aboutBean.setJavaInformation(javaInformation);
-        }
-
-        { // build info
-            final AboutApplicationBean.BuildInformation buildInformation = new AboutApplicationBean.BuildInformation();
-            buildInformation.setBuildTime(PwmConstants.BUILD_TIME);
-            buildInformation.setBuildNumber(PwmConstants.BUILD_NUMBER);
-            buildInformation.setBuildType(PwmConstants.BUILD_TYPE);
-            buildInformation.setBuildUser(PwmConstants.BUILD_USER);
-            buildInformation.setBuildRevision(PwmConstants.BUILD_REVISION);
-            buildInformation.setBuildJavaVendor(PwmConstants.BUILD_JAVA_VENDOR);
-            buildInformation.setBuildJavaVersion(PwmConstants.BUILD_JAVA_VERSION);
-            buildInformation.setBuildVersion(PwmConstants.BUILD_VERSION);
-            aboutBean.setBuildInformation(buildInformation);
-        }
-
-        return aboutBean;
-    }
-
     public enum Page {
         dashboard(PwmConstants.JSP_URL.ADMIN_DASHBOARD),
         analysis(PwmConstants.JSP_URL.ADMIN_ANALYSIS),

+ 1 - 1
pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java

@@ -174,7 +174,7 @@ public class CaptchaServlet extends PwmServlet {
                     Collections.singletonMap("Content-Type",PwmConstants.ContentTypeValue.form.getHeaderValue())
             );
             LOGGER.debug(pwmRequest, "sending reCaptcha verification request" );
-            final PwmHttpClient client = new PwmHttpClient(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession());
+            final PwmHttpClient client = new PwmHttpClient(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
             final PwmHttpClientResponse clientResponse = client.makeRequest(clientRequest);
 
             if (clientResponse.getStatusCode() != 200) {

+ 37 - 34
pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java

@@ -29,7 +29,6 @@ import password.pwm.Validator;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.*;
-import password.pwm.config.option.RecoveryVerificationMethod;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.ValueFactory;
 import password.pwm.config.value.X509CertificateValue;
@@ -77,7 +76,7 @@ public class ConfigEditorServlet extends PwmServlet {
         cancelEditing(HttpMethod.POST),
         uploadFile(HttpMethod.POST),
         setOption(HttpMethod.POST),
-        menuTreeData(HttpMethod.GET),
+        menuTreeData(HttpMethod.POST),
         settingData(HttpMethod.GET),
         testMacro(HttpMethod.POST),
 
@@ -127,6 +126,7 @@ public class ConfigEditorServlet extends PwmServlet {
     public static class TemplateInfo implements Serializable {
         public String description;
         public String key;
+        public boolean hidden;
     }
 
     protected ConfigEditorAction readProcessAction(final PwmRequest request)
@@ -253,16 +253,15 @@ public class ConfigEditorServlet extends PwmServlet {
         try {
             Class implementingClass = Class.forName(functionName);
             SettingUIFunction function = (SettingUIFunction) implementingClass.newInstance();
-            final String result = function.provideFunction(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), configManagerBean.getStoredConfiguration(), pwmSetting, profileID);
+            final Serializable result = function.provideFunction(pwmRequest, configManagerBean.getStoredConfiguration(), pwmSetting, profileID);
             RestResultBean restResultBean = new RestResultBean();
-            restResultBean.setSuccessMessage(result);
+            restResultBean.setSuccessMessage(Message.Success_Unknown.getLocalizedMessage(pwmRequest.getLocale(),pwmRequest.getConfig()));
+            restResultBean.setData(result);
             pwmRequest.outputJsonResult(restResultBean);
         } catch (Exception e) {
             final RestResultBean restResultBean;
             if (e instanceof PwmException) {
-                final String errorMsg = "error while loading data: " + ((PwmException) e).getErrorInformation().getDetailedErrorMsg();
-                final ErrorInformation errorInformation = new ErrorInformation(((PwmException) e).getError(), errorMsg);
-                restResultBean = RestResultBean.fromError(errorInformation, pwmRequest);
+                restResultBean = RestResultBean.fromError(((PwmException) e).getErrorInformation(), pwmRequest, true);
             } else {
                 restResultBean = new RestResultBean();
                 restResultBean.setError(true);
@@ -515,13 +514,13 @@ public class ConfigEditorServlet extends PwmServlet {
                 final String requestedTemplate = pwmRequest.readParameterAsString("template");
                 if (requestedTemplate != null && requestedTemplate.length() > 0) {
                     try {
-                        final PwmSetting.Template template = PwmSetting.Template.valueOf(requestedTemplate);
+                        final PwmSettingTemplate template = PwmSettingTemplate.valueOf(requestedTemplate);
                         configManagerBean.getStoredConfiguration().writeConfigProperty(
                                 StoredConfiguration.ConfigProperty.PROPERTY_KEY_TEMPLATE, template.toString());
                         LOGGER.trace("setting template to: " + requestedTemplate);
                     } catch (IllegalArgumentException e) {
                         configManagerBean.getStoredConfiguration().writeConfigProperty(
-                                StoredConfiguration.ConfigProperty.PROPERTY_KEY_TEMPLATE, PwmSetting.Template.DEFAULT.toString());
+                                StoredConfiguration.ConfigProperty.PROPERTY_KEY_TEMPLATE, PwmSettingTemplate.DEFAULT.toString());
                         LOGGER.error("unknown template set request: " + requestedTemplate);
                     }
                 }
@@ -556,7 +555,7 @@ public class ConfigEditorServlet extends PwmServlet {
         final RestResultBean restResultBean = new RestResultBean();
         final String searchTerm = valueMap.get("search");
         if (searchTerm != null && !searchTerm.isEmpty()) {
-            final ArrayList<StoredConfiguration.ConfigRecordID> searchResults = new ArrayList(configManagerBean.getStoredConfiguration().search(searchTerm, locale));
+            final ArrayList<StoredConfiguration.ConfigRecordID> searchResults = new ArrayList<>(configManagerBean.getStoredConfiguration().search(searchTerm, locale));
             final TreeMap<String, Map<String, Map<String, Object>>> returnData = new TreeMap<>();
 
             for (final StoredConfiguration.ConfigRecordID recordID : searchResults) {
@@ -565,8 +564,9 @@ public class ConfigEditorServlet extends PwmServlet {
                     final LinkedHashMap<String, Object> settingData = new LinkedHashMap<>();
                     settingData.put("category", setting.getCategory().toString());
                     settingData.put("value", configManagerBean.getStoredConfiguration().readSetting(setting, recordID.getProfileID()).toDebugString(true, pwmRequest.getLocale()));
-                    settingData.put("navigation", setting.getCategory().toMenuLocationDebug(null, locale));
+                    settingData.put("navigation", setting.getCategory().toMenuLocationDebug(recordID.getProfileID(), locale));
                     settingData.put("default", configManagerBean.getStoredConfiguration().isDefaultValue(setting, recordID.getProfileID()));
+                    settingData.put("profile",recordID.getProfileID());
 
                     final String returnCategory = settingData.get("navigation").toString();
                     if (!returnData.containsKey(returnCategory)) {
@@ -706,8 +706,11 @@ public class ConfigEditorServlet extends PwmServlet {
     {
         final Date startTime = new Date();
         final ArrayList<Map<String, Object>> navigationData = new ArrayList<>();
-        final boolean modifiedSettingsOnly = pwmRequest.readParameterAsBoolean("modifiedSettingsOnly");
-        final int level = pwmRequest.readParameterAsInt("level",-1);
+
+        final Map<String,Object> inputParameters = pwmRequest.readBodyAsJsonMap(false);
+        final boolean modifiedSettingsOnly = (boolean)inputParameters.get("modifiedSettingsOnly");
+        final double level = (double)inputParameters.get("level");
+        final String filterText = (String)inputParameters.get("text");
 
         { // root node
             final Map<String, Object> categoryInfo = new HashMap<>();
@@ -726,7 +729,7 @@ public class ConfigEditorServlet extends PwmServlet {
 
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
         for (final PwmSettingCategory loopCategory : PwmSettingCategory.sortedValues(pwmRequest.getLocale())) {
-            if (NavTreeHelper.categoryMatcher(loopCategory, storedConfiguration, modifiedSettingsOnly, level)) {
+            if (NavTreeHelper.categoryMatcher(loopCategory, storedConfiguration, modifiedSettingsOnly, (int)level, filterText)) {
                 final Map<String, Object> categoryInfo = new LinkedHashMap<>();
                 categoryInfo.put("id", loopCategory.getKey());
                 categoryInfo.put("name", loopCategory.getLabel(pwmRequest.getLocale()));
@@ -839,14 +842,15 @@ public class ConfigEditorServlet extends PwmServlet {
                 PwmSettingCategory category,
                 StoredConfiguration storedConfiguration,
                 final boolean modifiedOnly,
-                final int minLevel
+                final int minLevel,
+                final String text
         ) {
             if (category.isHidden()) {
                 return false;
             }
 
             for (PwmSettingCategory childCategory : category.getChildCategories()) {
-                if (categoryMatcher(childCategory, storedConfiguration, modifiedOnly, minLevel)) {
+                if (categoryMatcher(childCategory, storedConfiguration, modifiedOnly, minLevel, text)) {
                     return true;
                 }
             }
@@ -854,14 +858,14 @@ public class ConfigEditorServlet extends PwmServlet {
             if (category.hasProfiles()) {
                 for (String profileID : storedConfiguration.profilesForSetting(category.getProfileSetting())) {
                     for (final PwmSetting setting : category.getSettings()) {
-                        if (settingMatches(storedConfiguration,setting,profileID,modifiedOnly,minLevel)) {
+                        if (settingMatches(storedConfiguration,setting,profileID,modifiedOnly,minLevel,text)) {
                             return true;
                         }
                     }
                 }
             } else {
                 for (final PwmSetting setting : category.getSettings()) {
-                    if (settingMatches(storedConfiguration,setting,null,modifiedOnly,minLevel)) {
+                    if (settingMatches(storedConfiguration,setting,null,modifiedOnly,minLevel,text)) {
                         return true;
                     }
                 }
@@ -874,7 +878,8 @@ public class ConfigEditorServlet extends PwmServlet {
                 final PwmSetting setting,
                 final String profileID,
                 final boolean modifiedOnly,
-                final int level
+                final int level,
+                final String text
         ) {
             if (setting.isHidden()) {
                 return false;
@@ -886,12 +891,20 @@ public class ConfigEditorServlet extends PwmServlet {
                 }
             }
 
-            if (level < 0) {
-                return true;
+            if (level > 0 && setting.getLevel() > level) {
+                return false;
             }
 
-            if (setting.getLevel() <= level) {
+
+            if (text == null || text.isEmpty()) {
                 return true;
+            } else {
+                final StoredValue storedValue = storedConfiguration.readSetting(setting,profileID);
+                for (final String term : StringUtil.whitespaceSplit(text)) {
+                    if (storedConfiguration.matchSetting(setting, storedValue, term, PwmConstants.DEFAULT_LOCALE)) {
+                        return true;
+                    }
+                }
             }
 
             return false;
@@ -949,25 +962,15 @@ public class ConfigEditorServlet extends PwmServlet {
         }
         {
             final LinkedHashMap<String, Object> templateMap = new LinkedHashMap<>();
-            for (final PwmSetting.Template template : PwmSetting.Template.values()) {
+            for (final PwmSettingTemplate template : PwmSettingTemplate.sortedValues(pwmRequest.getLocale())) {
                 final TemplateInfo templateInfo = new TemplateInfo();
                 templateInfo.description = template.getLabel(locale);
                 templateInfo.key = template.toString();
+                templateInfo.hidden = template.isHidden();
                 templateMap.put(template.toString(), templateInfo);
             }
             returnMap.put("templates", templateMap);
         }
-        {
-            final LinkedHashMap<String, Object> verificationMethodMap = new LinkedHashMap<>();
-            for (final RecoveryVerificationMethod recoveryVerificationMethod : RecoveryVerificationMethod.values()) {
-                final String displayLabel = LocaleHelper.getLocalizedMessage(
-                        pwmRequest.getLocale(),
-                        recoveryVerificationMethod.getConfigDisplayKey(),
-                        pwmRequest.getConfig());
-                verificationMethodMap.put(recoveryVerificationMethod.toString(),displayLabel);
-            }
-            returnMap.put("verificationMethods",verificationMethodMap);
-        }
 
         final RestResultBean restResultBean = new RestResultBean();
 

+ 12 - 20
pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java

@@ -56,6 +56,7 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.Serializable;
 import java.net.URI;
 import java.security.cert.X509Certificate;
 import java.util.*;
@@ -147,7 +148,7 @@ public class ConfigGuideServlet extends PwmServlet {
     }
 
 
-    public static Map<String,String> defaultForm(PwmSetting.Template template) {
+    public static Map<String,String> defaultForm(PwmSettingTemplate template) {
         final Map<String,String> defaultLdapForm = new HashMap<>();
 
         try {
@@ -217,7 +218,7 @@ public class ConfigGuideServlet extends PwmServlet {
             try {
                 final URI ldapServerUri = new URI(ldapServerString);
                 if ("ldaps".equalsIgnoreCase(ldapServerUri.getScheme())) {
-                    configGuideBean.setLdapCertificates(X509Utils.readLdapServerCerts(ldapServerUri));
+                    configGuideBean.setLdapCertificates(X509Utils.readRemoteCertificates(ldapServerUri));
                     configGuideBean.setCertsTrustedbyKeystore(X509Utils.testIfLdapServerCertsInDefaultKeystore(ldapServerUri));
                 } else {
                     configGuideBean.setLdapCertificates(null);
@@ -358,6 +359,7 @@ public class ConfigGuideServlet extends PwmServlet {
                 if (records.isEmpty()) {
                     records.add(new HealthRecord(HealthStatus.GOOD, HealthTopic.LDAP, "LDAP Contextless Login Root validated"));
                 }
+                    /*
                 try {
                     final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
                     final Map<String, List<String>> results = userMatchViewerFunction.discoverMatchingUsers(
@@ -367,6 +369,7 @@ public class ConfigGuideServlet extends PwmServlet {
                             PwmSetting.QUERY_MATCH_PWM_ADMIN,
                             null
                     );
+
                     if (results.isEmpty()) {
                         records.add(new HealthRecord(HealthStatus.WARN, HealthTopic.LDAP, "No matching admin users"));
                     } else {
@@ -377,6 +380,7 @@ public class ConfigGuideServlet extends PwmServlet {
                 } catch (Exception e) {
                     records.add(new HealthRecord(HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getMessage()));
                 }
+                    */
             }
             break;
 
@@ -406,23 +410,11 @@ public class ConfigGuideServlet extends PwmServlet {
             final PwmRequest pwmRequest,
             final ConfigGuideBean configGuideBean
     ) throws IOException, ServletException {
-        final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
-        final int maxResults = 1000;
+
         try {
-            final Map<String, List<String>> results = userMatchViewerFunction.discoverMatchingUsers(
-                    pwmRequest.getPwmApplication(),
-                    1000,
-                    configGuideBean.getStoredConfiguration(),
-                    PwmSetting.QUERY_MATCH_PWM_ADMIN,
-                    null
-            );
-            final String htmlResults = userMatchViewerFunction.convertResultsToHtmlTable(
-                    pwmRequest.getPwmApplication(),
-                    pwmRequest.getLocale(),
-                    results,
-                    maxResults
-            );
-            pwmRequest.outputJsonResult(new RestResultBean(htmlResults));
+            final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
+            final Serializable output = userMatchViewerFunction.provideFunction(pwmRequest, configGuideBean.getStoredConfiguration(), PwmSetting.QUERY_MATCH_PWM_ADMIN, null);
+            pwmRequest.outputJsonResult(new RestResultBean(output));
         } catch (PwmException e) {
             LOGGER.error(pwmRequest,e.getErrorInformation());
             pwmRequest.respondWithError(e.getErrorInformation());
@@ -448,7 +440,7 @@ public class ConfigGuideServlet extends PwmServlet {
 
         if (incomingFormData != null && incomingFormData.get(PARAM_TEMPLATE_NAME) != null && !incomingFormData.get(PARAM_TEMPLATE_NAME).isEmpty()) {
             try {
-                final PwmSetting.Template template = PwmSetting.Template.valueOf(incomingFormData.get(PARAM_TEMPLATE_NAME));
+                final PwmSettingTemplate template = PwmSettingTemplate.valueOf(incomingFormData.get(PARAM_TEMPLATE_NAME));
                 if (configGuideBean.getSelectedTemplate() != template) {
                     LOGGER.debug(pwmRequest, "resetting form defaults using " + template.toString() + " template");
                     final Map<String, String> defaultForm = defaultForm(template);
@@ -488,7 +480,7 @@ public class ConfigGuideServlet extends PwmServlet {
         }
 
         final boolean ldapSchemaPermitted = "LDAP".equals(configGuideBean.getFormData().get(PARAM_CR_STORAGE_PREF))
-                && configGuideBean.getSelectedTemplate() == PwmSetting.Template.NOVL;
+                && configGuideBean.getSelectedTemplate() == PwmSettingTemplate.NOVL;
 
         if ("NEXT".equals(requestedStep)) {
             step = configGuideBean.getStep().next();

+ 89 - 31
pwm/servlet/src/password/pwm/http/servlet/ConfigManagerServlet.java

@@ -23,6 +23,7 @@
 package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
+import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.*;
 import password.pwm.config.Configuration;
@@ -76,7 +77,7 @@ public class ConfigManagerServlet extends PwmServlet {
         importLocalDB(HttpMethod.POST),
         summary(HttpMethod.GET),
         viewLog(HttpMethod.GET),
-        
+
         ;
 
         private final HttpMethod method;
@@ -171,7 +172,7 @@ public class ConfigManagerServlet extends PwmServlet {
                     : PwmConstants.DEFAULT_DATETIME_FORMAT.format(lastModifyTime);
             pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigLastModified, output);
         }
-        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigHasPassword, LocaleHelper.booleanString(configurationReader.getStoredConfiguration().hasPassword(),pwmRequest.getLocale(),pwmRequest.getConfig()));
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigHasPassword, LocaleHelper.booleanString(configurationReader.getStoredConfiguration().hasPassword(), pwmRequest.getLocale(), pwmRequest.getConfig()));
     }
 
     void restUploadLocalDB(final PwmRequest pwmRequest)
@@ -349,12 +350,12 @@ public class ConfigManagerServlet extends PwmServlet {
             }
         }
 
+        final int persistentSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS));
         if ((persistentLoginAccepted || passwordAccepted)) {
             configManagerBean.setPasswordVerified(true);
             pwmApplication.getIntruderManager().convenience().clearAddressAndSession(pwmSession);
             pwmApplication.getIntruderManager().clear(RecordType.USERNAME,CONFIGMANAGER_INTRUDER_USERNAME);
             if (persistentLoginEnabled && !persistentLoginAccepted && "on".equals(pwmRequest.readParameterAsString("remember"))) {
-                final int persistentSeconds = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS));
                 if (persistentSeconds > 0) {
                     final Date expirationDate = new Date(System.currentTimeMillis() + (persistentSeconds * 1000));
                     final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo(expirationDate, persistentLoginValue);
@@ -386,6 +387,9 @@ public class ConfigManagerServlet extends PwmServlet {
         if (configManagerBean.getPrePasswordEntryUrl() == null) {
             configManagerBean.setPrePasswordEntryUrl(pwmRequest.getHttpServletRequest().getRequestURL().toString());
         }
+
+        final String time = new TimeDuration(persistentSeconds * 1000).asLongString(pwmRequest.getLocale());
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigPasswordRememberTime,time);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_LOGIN);
         return true;
     }
@@ -575,9 +579,20 @@ public class ConfigManagerServlet extends PwmServlet {
             zipOutput.flush();
         }
         {
-            final String aboutJson = JsonUtil.serialize(AdminServlet.makeInfoBean(pwmApplication), JsonUtil.Flag.PrettyPrint);
-            zipOutput.putNextEntry(new ZipEntry(pathPrefix + "about.json"));
-            zipOutput.write(aboutJson.getBytes(PwmConstants.DEFAULT_CHARSET));
+            final Properties outputProps = new Properties() {
+                public synchronized Enumeration<Object> keys() {
+                    return Collections.enumeration(new TreeSet<>(super.keySet()));
+                }
+            };
+
+            final Map<PwmAboutProperty,String> infoBean = Helper.makeInfoBean(pwmApplication);
+            for (final PwmAboutProperty aboutProperty : infoBean.keySet()) {
+                outputProps.put(aboutProperty.toString(), infoBean.get(aboutProperty));
+            }
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            outputProps.store(baos,PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
+            zipOutput.putNextEntry(new ZipEntry(pathPrefix + "about.properties"));
+            zipOutput.write(baos.toByteArray());
             zipOutput.closeEntry();
             zipOutput.flush();
         }
@@ -607,24 +622,84 @@ public class ConfigManagerServlet extends PwmServlet {
             }
 
             // java threads
-            outputMap.put("threads",Thread.getAllStackTraces());
+            outputMap.put("threads", Thread.getAllStackTraces());
 
             final String recordJson = JsonUtil.serializeMap(outputMap, JsonUtil.Flag.PrettyPrint);
             zipOutput.write(recordJson.getBytes(PwmConstants.DEFAULT_CHARSET));
             zipOutput.closeEntry();
             zipOutput.flush();
         }
-        if (pwmApplication.getApplicationPath() != null) {
-            try {
-                zipOutput.putNextEntry(new ZipEntry(pathPrefix + "fileMd5sums.json"));
-                final Map<String,String> fileChecksums = BuildChecksumMaker.readDirectorySums(pwmApplication.getApplicationPath());
-                final String json = JsonUtil.serializeMap(fileChecksums, JsonUtil.Flag.PrettyPrint);
+        {
+            final List<BuildChecksumMaker.FileInformation> fileInformations = new ArrayList<>();
+            if (pwmApplication.getApplicationPath() != null) {
+                try {
+                    fileInformations.addAll(BuildChecksumMaker.readFileInformation(pwmApplication.getApplicationPath()));
+                } catch (Exception e) {
+                    LOGGER.error(pwmSession, "unable to generate appPath fileMd5sums during zip debug building: " + e.getMessage());
+                }
+            }
+            if (pwmApplication.getWebInfPath() != null && !pwmApplication.getWebInfPath().equals(pwmApplication.getApplicationPath())) {
+                try {
+                    fileInformations.addAll(BuildChecksumMaker.readFileInformation(pwmApplication.getWebInfPath()));
+                } catch (Exception e) {
+                    LOGGER.error(pwmSession, "unable to generate webInfPath fileMd5sums during zip debug building: " + e.getMessage());
+                }
+            }
+            {
+                zipOutput.putNextEntry(new ZipEntry(pathPrefix + "fileinformation.json"));
+                final String json = JsonUtil.serializeCollection(fileInformations, JsonUtil.Flag.PrettyPrint);
                 zipOutput.write(json.getBytes(PwmConstants.DEFAULT_CHARSET));
                 zipOutput.closeEntry();
                 zipOutput.flush();
-            } catch (Exception e) {
-                LOGGER.error(pwmSession,"unable to generate fileMd5sums during zip debug building: " + e.getMessage());
             }
+            {
+                zipOutput.putNextEntry(new ZipEntry(pathPrefix + "fileinformation.csv"));
+                final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                final CSVPrinter csvPrinter = Helper.makeCsvPrinter(byteArrayOutputStream);
+                {
+                    final List<String> headerRow = new ArrayList<>();
+                    headerRow.add("Filename");
+                    headerRow.add("Filepath");
+                    headerRow.add("Last Modified");
+                    headerRow.add("Size");
+                    headerRow.add("sha1sum");
+                    csvPrinter.printRecord(headerRow);
+                }
+                for (final BuildChecksumMaker.FileInformation fileInformation : fileInformations) {
+                    final List<String> headerRow = new ArrayList<>();
+                    headerRow.add(fileInformation.getFilename());
+                    headerRow.add(fileInformation.getFilepath());
+                    headerRow.add(PwmConstants.DEFAULT_DATETIME_FORMAT.format(fileInformation.getModified()));
+                    headerRow.add(String.valueOf(fileInformation.getSize()));
+                    headerRow.add(fileInformation.getSha1sum());
+                    csvPrinter.printRecord(headerRow);
+                }
+                csvPrinter.flush();
+                zipOutput.write(byteArrayOutputStream.toByteArray());
+                zipOutput.closeEntry();
+                zipOutput.flush();
+            }
+        }
+        {
+            final List<BuildChecksumMaker.FileInformation> fileInformation = new ArrayList<>();
+            if (pwmApplication.getApplicationPath() != null) {
+                try {
+                    fileInformation.addAll(BuildChecksumMaker.readFileInformation(pwmApplication.getApplicationPath()));
+                } catch (Exception e) {
+                    LOGGER.error(pwmSession, "unable to generate appPath fileMd5sums during zip debug building: " + e.getMessage());
+                }
+            }
+            if (pwmApplication.getWebInfPath() != null && !pwmApplication.getWebInfPath().equals(pwmApplication.getApplicationPath())) {
+                try {
+                    fileInformation.addAll(BuildChecksumMaker.readFileInformation(pwmApplication.getWebInfPath()));
+                } catch (Exception e) {
+                    LOGGER.error(pwmSession, "unable to generate webInfPath fileMd5sums during zip debug building: " + e.getMessage());
+                }
+            }
+            final String json = JsonUtil.serializeCollection(fileInformation, JsonUtil.Flag.PrettyPrint);
+            zipOutput.write(json.getBytes(PwmConstants.DEFAULT_CHARSET));
+            zipOutput.closeEntry();
+            zipOutput.flush();
         }
         {
             zipOutput.putNextEntry(new ZipEntry(pathPrefix + "debug.log"));
@@ -721,22 +796,5 @@ public class ConfigManagerServlet extends PwmServlet {
         pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ConfigurationSummaryOutput,outputMap);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.CONFIG_MANAGER_EDITOR_SUMMARY);
     }
-
-    private void processViewLog(
-            final PwmRequest pwmRequest
-    )
-            throws PwmUnrecoverableException, IOException, ServletException
-    {
-
-        final PwmApplication.MODE configMode = pwmRequest.getPwmApplication().getApplicationMode();
-        if (configMode != PwmApplication.MODE.CONFIGURATION) {
-            if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) {
-                throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE,"admin permission required"));
-            }
-        }
-        pwmRequest.forwardToJsp(PwmConstants.JSP_URL.ADMIN_LOGVIEW_WINDOW);
-    }
-
-
 }
 

+ 114 - 72
pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java

@@ -33,11 +33,12 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.RecoveryVerificationMethod;
 import password.pwm.bean.*;
 import password.pwm.config.*;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.RecoveryAction;
-import password.pwm.config.option.RecoveryVerificationMethod;
+import password.pwm.config.option.RecoveryVerificationMethods;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileUtility;
@@ -73,6 +74,7 @@ import password.pwm.util.otp.OTPUserRecord;
 import password.pwm.util.stats.Statistic;
 import password.pwm.util.stats.StatisticsManager;
 import password.pwm.ws.client.rest.RestTokenDataClient;
+import password.pwm.ws.client.rest.naaf.PwmNAAFVerificationMethod;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
@@ -102,6 +104,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
         actionChoice(HttpMethod.POST),
         tokenChoice(HttpMethod.POST),
         verificationChoice(HttpMethod.POST),
+        enterNaafResponse(HttpMethod.POST),
 
         ;
 
@@ -202,6 +205,11 @@ public class ForgottenPasswordServlet extends PwmServlet {
                 case verificationChoice:
                     this.processVerificationChoice(pwmRequest);
                     break;
+
+                case enterNaafResponse:
+                    this.processEnterNaaf(pwmRequest);
+                    break;
+
             }
         } else {
             pwmRequest.getPwmSession().clearForgottenPasswordBean();
@@ -261,13 +269,13 @@ public class ForgottenPasswordServlet extends PwmServlet {
     {
         final ForgottenPasswordBean forgottenPasswordBean = pwmRequest.getPwmSession().getForgottenPasswordBean();
         final String requestedChoiceStr = pwmRequest.readParameterAsString("choice");
-        final LinkedHashSet<RecoveryVerificationMethod> remainingAvailableOptionalMethods = new LinkedHashSet<>(figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean));
+        final LinkedHashSet<RecoveryVerificationMethods> remainingAvailableOptionalMethods = new LinkedHashSet<>(figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean));
         pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.AvailableAuthMethods, remainingAvailableOptionalMethods);
 
-        RecoveryVerificationMethod requestedChoice = null;
+        RecoveryVerificationMethods requestedChoice = null;
         if (requestedChoiceStr != null && !requestedChoiceStr.isEmpty()) {
             try {
-                requestedChoice = RecoveryVerificationMethod.valueOf(requestedChoiceStr);
+                requestedChoice = RecoveryVerificationMethods.valueOf(requestedChoiceStr);
             } catch (IllegalArgumentException e) {
                 final String errorMsg = "unknown verification method requested";
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER,errorMsg);
@@ -394,7 +402,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                             forgottenPasswordBean
                     );
                 }
-                forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethod.TOKEN);
+                forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.TOKEN);
                 StatisticsManager.incrementStat(pwmRequest.getPwmApplication(), Statistic.RECOVERY_TOKENS_PASSED);
             }
         } catch (PwmOperationalException e) {
@@ -402,7 +410,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
             errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT,errorMsg);
         }
 
-        if (!forgottenPasswordBean.getProgress().getSatisfiedMethods().contains(RecoveryVerificationMethod.TOKEN)) {
+        if (!forgottenPasswordBean.getProgress().getSatisfiedMethods().contains(RecoveryVerificationMethods.TOKEN)) {
             if (errorInformation == null) {
                 errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT);
             }
@@ -410,6 +418,45 @@ public class ForgottenPasswordServlet extends PwmServlet {
         }
     }
 
+    private void processEnterNaaf(final PwmRequest pwmRequest)
+            throws PwmUnrecoverableException, IOException, ServletException
+    {
+        final String PREFIX = "naaf-";
+        final ForgottenPasswordBean forgottenPasswordBean = pwmRequest.getPwmSession().getForgottenPasswordBean();
+        final RecoveryVerificationMethod naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
+
+        final Map<String,String> naafResponses = new LinkedHashMap<>();
+        {
+            final Map<String,String> inputMap = pwmRequest.readParametersAsMap();
+            for (final String name : inputMap.keySet()) {
+                if (name != null && name.startsWith(PREFIX)) {
+                    final String strippedName = name.substring(PREFIX.length(), name.length());
+                    final String value = inputMap.get(name);
+                    naafResponses.put(strippedName,value);
+                }
+            }
+        }
+
+        final ErrorInformation errorInformation = naafMethod.respondToPrompts(naafResponses);
+
+        if (naafMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.COMPLETE) {
+            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.NAAF);
+        }
+
+        if (naafMethod.getVerificationState() == RecoveryVerificationMethod.VerificationState.FAILED) {
+            forgottenPasswordBean.getProgress().setNaafRecoveryMethod(null);
+            pwmRequest.respondWithError(errorInformation,true);
+            handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
+            LOGGER.debug(pwmRequest, "unsuccessful NAAF verification input: " + errorInformation.toDebugStr());
+            return;
+        }
+
+        if (errorInformation != null) {
+            pwmRequest.setResponseError(errorInformation);
+            handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, errorInformation);
+        }
+    }
+
     private void processEnterOtpToken(final PwmRequest pwmRequest)
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
@@ -434,7 +481,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                 if (otpPassed) {
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_PASSED);
                     LOGGER.debug(pwmRequest, "one time password validation has been passed");
-                    forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethod.OTP);
+                    forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.OTP);
                 } else {
                     StatisticsManager.incrementStat(pwmRequest, Statistic.RECOVERY_OTP_FAILED);
                     handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_OTP_TOKEN));
@@ -498,7 +545,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
             return;
         }
 
-        forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethod.CHALLENGE_RESPONSES);
+        forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.CHALLENGE_RESPONSES);
     }
 
     private void processCheckAttributes(final PwmRequest pwmRequest)
@@ -539,7 +586,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                 }
             }
 
-            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethod.ATTRIBUTES);
+            forgottenPasswordBean.getProgress().getSatisfiedMethods().add(RecoveryVerificationMethods.ATTRIBUTES);
         } catch (PwmDataValidationException e) {
             handleUserVerificationBadAttempt(pwmRequest, forgottenPasswordBean, new ErrorInformation(PwmError.ERROR_INCORRECT_RESPONSE,e.getErrorInformation().toDebugStr()));
         }
@@ -566,15 +613,18 @@ public class ForgottenPasswordServlet extends PwmServlet {
             return;
         }
 
-        if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethod.PREVIOUS_AUTH)) {
-            if (checkAuthRecord(pwmRequest, forgottenPasswordBean.getUserInfo().getUserGuid())) {
-                LOGGER.debug(pwmRequest, "marking " + RecoveryVerificationMethod.PREVIOUS_AUTH + " method as satisfied");
-                progress.getSatisfiedMethods().add(RecoveryVerificationMethod.PREVIOUS_AUTH);
+        // check for previous authentication
+        if (recoveryFlags.getRequiredAuthMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH) || recoveryFlags.getOptionalAuthMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH)) {
+            if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethods.PREVIOUS_AUTH)) {
+                if (checkAuthRecord(pwmRequest, forgottenPasswordBean.getUserInfo().getUserGuid())) {
+                    LOGGER.debug(pwmRequest, "marking " + RecoveryVerificationMethods.PREVIOUS_AUTH + " method as satisfied");
+                    progress.getSatisfiedMethods().add(RecoveryVerificationMethods.PREVIOUS_AUTH);
+                }
             }
         }
 
         // dispatch required auth methods.
-        for (final RecoveryVerificationMethod method : recoveryFlags.getRequiredAuthMethods()) {
+        for (final RecoveryVerificationMethods method : recoveryFlags.getRequiredAuthMethods()) {
             if (!progress.getSatisfiedMethods().contains(method)) {
                 forwardUserBasedOnRecoveryMethod(pwmRequest, method);
                 return;
@@ -594,9 +644,9 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         // check if more optional methods required
         if (recoveryFlags.getMinimumOptionalAuthMethods() > 0) {
-            final Set<RecoveryVerificationMethod> satisfiedOptionalMethods = figureSatisfiedOptionalAuthMethods(recoveryFlags,progress);
+            final Set<RecoveryVerificationMethods> satisfiedOptionalMethods = figureSatisfiedOptionalAuthMethods(recoveryFlags,progress);
             if (satisfiedOptionalMethods.size() < recoveryFlags.getMinimumOptionalAuthMethods()) {
-                final Set<RecoveryVerificationMethod> remainingAvailableOptionalMethods = figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean);
+                final Set<RecoveryVerificationMethods> remainingAvailableOptionalMethods = figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean);
                 if (remainingAvailableOptionalMethods.isEmpty()) {
                     final String errorMsg = "additional optional verification methods are needed, however all available optional verification methods have been satisified by user";
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_INVALID_CONFIG,errorMsg);
@@ -604,7 +654,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                     throw new PwmUnrecoverableException(errorInformation);
                 } else {
                     if (remainingAvailableOptionalMethods.size() == 1) {
-                        final RecoveryVerificationMethod remainingMethod = remainingAvailableOptionalMethods.iterator().next();
+                        final RecoveryVerificationMethods remainingMethod = remainingAvailableOptionalMethods.iterator().next();
                         LOGGER.debug(pwmRequest, "only 1 remaining available optional verification method, will redirect to " + remainingMethod.toString());
                         forwardUserBasedOnRecoveryMethod(pwmRequest, remainingMethod);
                         progress.setInProgressVerificationMethod(remainingMethod);
@@ -843,6 +893,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
         final UserIdentity userIdentity = userInfoBean.getUserIdentity();
         final Map<String,String> tokenMapData = new HashMap<>();
 
+
         try {
             final Date userLastPasswordChange = PasswordUtility.determinePwdLastModified(
                     pwmRequest.getPwmApplication(),
@@ -857,8 +908,11 @@ public class ForgottenPasswordServlet extends PwmServlet {
             LOGGER.error(pwmRequest, "unexpected error reading user's last password change time");
         }
 
+        final EmailItemBean emailItemBean = config.readSettingAsEmail(PwmSetting.EMAIL_CHALLENGE_TOKEN, pwmRequest.getLocale());
+        final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
+
         final RestTokenDataClient.TokenDestinationData inputDestinationData = new RestTokenDataClient.TokenDestinationData(
-                userInfoBean.getUserEmailAddress(),
+                macroMachine.expandMacros(emailItemBean.getTo()),
                 userInfoBean.getUserSmsNumber(),
                 null
         );
@@ -890,11 +944,8 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         LOGGER.debug(pwmRequest, "generated token code for session");
 
-        final EmailItemBean emailItemBean = config.readSettingAsEmail(PwmSetting.EMAIL_CHALLENGE_TOKEN, pwmRequest.getLocale());
         final String smsMessage = config.readSettingAsLocalizedString(PwmSetting.SMS_CHALLENGE_TOKEN_TEXT, pwmRequest.getLocale());
 
-        final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
-
         TokenService.TokenSender.sendToken(
                 pwmRequest.getPwmApplication(),
                 userInfoBean,
@@ -997,8 +1048,9 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
     private static void verifyRequirementsForAuthMethod(
             final ForgottenPasswordBean forgottenPasswordBean,
-            final RecoveryVerificationMethod recoveryVerificationMethod) throws PwmUnrecoverableException {
-        switch (recoveryVerificationMethod) {
+            final RecoveryVerificationMethods recoveryVerificationMethods) throws PwmUnrecoverableException
+    {
+        switch (recoveryVerificationMethods) {
             case TOKEN: {
                 final MessageSendMethod tokenSendMethod = forgottenPasswordBean.getRecoveryFlags().getTokenSendMethod();
                 if (tokenSendMethod == null || tokenSendMethod == MessageSendMethod.NONE) {
@@ -1090,8 +1142,8 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         final ResponseSet responseSet;
         final ChallengeSet challengeSet;
-        if (recoveryFlags.getRequiredAuthMethods().contains(RecoveryVerificationMethod.CHALLENGE_RESPONSES)
-                || recoveryFlags.getOptionalAuthMethods().contains(RecoveryVerificationMethod.CHALLENGE_RESPONSES)) {
+        if (recoveryFlags.getRequiredAuthMethods().contains(RecoveryVerificationMethods.CHALLENGE_RESPONSES)
+                || recoveryFlags.getOptionalAuthMethods().contains(RecoveryVerificationMethods.CHALLENGE_RESPONSES)) {
             try {
                 final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userInfoBean.getUserIdentity());
                 responseSet = pwmApplication.getCrService().readUserResponseSet(
@@ -1145,8 +1197,8 @@ public class ForgottenPasswordServlet extends PwmServlet {
         forgottenPasswordBean.setRecoveryFlags(recoveryFlags);
         forgottenPasswordBean.setProgress(new ForgottenPasswordBean.Progress());
 
-        for (final RecoveryVerificationMethod recoveryVerificationMethod : recoveryFlags.getRequiredAuthMethods()) {
-            verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethod);
+        for (final RecoveryVerificationMethods recoveryVerificationMethods : recoveryFlags.getRequiredAuthMethods()) {
+            verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
         }
     }
 
@@ -1159,8 +1211,8 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         final MessageSendMethod tokenSendMethod = config.getForgottenPasswordProfiles().get(forgottenPasswordProfileID).readSettingAsEnum(PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class);
 
-        final Set<RecoveryVerificationMethod> requiredRecoveryVerificationMethods = forgottenPasswordProfile.requiredRecoveryAuthenticationMethods();
-        final Set<RecoveryVerificationMethod> optionalRecoveryVerificationMethods = forgottenPasswordProfile.optionalRecoveryAuthenticationMethods();
+        final Set<RecoveryVerificationMethods> requiredRecoveryVerificationMethods = forgottenPasswordProfile.requiredRecoveryAuthenticationMethods();
+        final Set<RecoveryVerificationMethods> optionalRecoveryVerificationMethods = forgottenPasswordProfile.optionalRecoveryAuthenticationMethods();
         final int minimumOptionalRecoveryAuthMethods = forgottenPasswordProfile.getMinOptionalRequired();
         final boolean allowWhenLdapIntruderLocked = forgottenPasswordProfile.readSettingAsBoolean(PwmSetting.RECOVERY_ALLOW_WHEN_LOCKED);
 
@@ -1262,31 +1314,31 @@ public class ForgottenPasswordServlet extends PwmServlet {
         throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_TOKEN_MISSING_CONTACT));
     }
 
-    private static Set<RecoveryVerificationMethod> figureSatisfiedOptionalAuthMethods(
+    private static Set<RecoveryVerificationMethods> figureSatisfiedOptionalAuthMethods(
             ForgottenPasswordBean.RecoveryFlags recoveryFlags,
             ForgottenPasswordBean.Progress progress)
     {
-        final Set<RecoveryVerificationMethod> result = new HashSet<>();
+        final Set<RecoveryVerificationMethods> result = new HashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.retainAll(progress.getSatisfiedMethods());
         return Collections.unmodifiableSet(result);
     }
 
-    private static Set<RecoveryVerificationMethod> figureRemainingAvailableOptionalAuthMethods(
+    private static Set<RecoveryVerificationMethods> figureRemainingAvailableOptionalAuthMethods(
             final ForgottenPasswordBean forgottenPasswordBean
     )
     {
         ForgottenPasswordBean.RecoveryFlags recoveryFlags = forgottenPasswordBean.getRecoveryFlags();
         ForgottenPasswordBean.Progress progress = forgottenPasswordBean.getProgress();
-        final Set<RecoveryVerificationMethod> result = new HashSet<>();
+        final Set<RecoveryVerificationMethods> result = new HashSet<>();
         result.addAll(recoveryFlags.getOptionalAuthMethods());
         result.removeAll(progress.getSatisfiedMethods());
 
-        for (final RecoveryVerificationMethod recoveryVerificationMethod : new HashSet<>(result)) {
+        for (final RecoveryVerificationMethods recoveryVerificationMethods : new HashSet<>(result)) {
             try {
-                verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethod);
+                verifyRequirementsForAuthMethod(forgottenPasswordBean, recoveryVerificationMethods);
             } catch (PwmUnrecoverableException e) {
-                result.remove(recoveryVerificationMethod);
+                result.remove(recoveryVerificationMethods);
             }
         }
 
@@ -1301,7 +1353,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
     private void forwardUserBasedOnRecoveryMethod(
             final PwmRequest pwmRequest,
-            final RecoveryVerificationMethod method)
+            final RecoveryVerificationMethods method)
             throws ServletException, PwmUnrecoverableException, IOException
     {
         LOGGER.debug(pwmRequest,"attempting to forward request to handle verification method " + method.toString());
@@ -1345,13 +1397,37 @@ public class ForgottenPasswordServlet extends PwmServlet {
                     progress.setTokenSent(true);
                 }
 
-                if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethod.TOKEN)) {
+                if (!progress.getSatisfiedMethods().contains(RecoveryVerificationMethods.TOKEN)) {
                     pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_ENTER_TOKEN);
                     return;
                 }
             }
             break;
 
+            case NAAF:
+                final RecoveryVerificationMethod naafMethod;
+                if (forgottenPasswordBean.getProgress().getNaafRecoveryMethod() == null) {
+                    naafMethod = new PwmNAAFVerificationMethod();
+                    naafMethod.init(
+                            pwmRequest.getPwmApplication(),
+                            forgottenPasswordBean.getUserInfo(),
+                            pwmRequest.getSessionLabel(),
+                            pwmRequest.getLocale()
+                    );
+                    forgottenPasswordBean.getProgress().setNaafRecoveryMethod(naafMethod);
+                } else {
+                    naafMethod = forgottenPasswordBean.getProgress().getNaafRecoveryMethod();
+                }
+
+                final List<RecoveryVerificationMethod.UserPrompt> prompts = naafMethod.getCurrentPrompts();
+                final String displayInstructions = naafMethod.getCurrentDisplayInstructions();
+
+                pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordPrompts, new ArrayList<>(prompts));
+                pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordInstructions, displayInstructions);
+                pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_NAAF);
+
+                break;
+
             default:
                 throw new UnsupportedOperationException("unexpected method during forward: " + method.toString());
         }
@@ -1387,40 +1463,6 @@ public class ForgottenPasswordServlet extends PwmServlet {
         pwmRequest.addFormInfoToRequestAttr(PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM,false,false);
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_SEARCH);
     }
-
-
-    private void verifyTokenPayload(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final UserIdentity sessionUserIdentity,
-            final TokenPayload tokenPayload
-    )
-            throws PwmOperationalException
-    {
-        if (tokenPayload.getData().containsKey(PwmConstants.TOKEN_KEY_PWD_CHG_DATE)) {
-            return;
-        }
-
-        try {
-            final Date userLastPasswordChange = PasswordUtility.determinePwdLastModified(
-                    pwmApplication,
-                    sessionLabel,
-                    sessionUserIdentity);
-            final String dateStringInToken = tokenPayload.getData().get(PwmConstants.TOKEN_KEY_PWD_CHG_DATE);
-            if (userLastPasswordChange != null && dateStringInToken != null) {
-                final String userChangeString = PwmConstants.DEFAULT_DATETIME_FORMAT.format(userLastPasswordChange);
-                if (!dateStringInToken.equalsIgnoreCase(userChangeString)) {
-                    final String errorString = "user password has changed since token issued, token rejected";
-                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_EXPIRED, errorString);
-                    throw new PwmOperationalException(errorInformation);
-                }
-            }
-        } catch (ChaiUnavailableException | PwmUnrecoverableException e) {
-            final String errorMsg = "unexpected error reading user's last password change time while validating token: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_TOKEN_INCORRECT, errorMsg);
-            throw new PwmOperationalException(errorInformation);
-        }
-    }
 }
 
 

+ 32 - 10
pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java

@@ -39,6 +39,8 @@ import password.pwm.config.ActionConfiguration;
 import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.option.HelpdeskClearResponseMode;
+import password.pwm.config.option.HelpdeskUIMode;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.error.*;
@@ -183,7 +185,7 @@ public class HelpdeskServlet extends PwmServlet {
                     return;
 
                 case clientData:
-                    restClientData(pwmRequest, helpdeskProfile);
+                    restClientData(pwmRequest, helpdeskBean, helpdeskProfile);
                     return;
             }
         }
@@ -191,16 +193,34 @@ public class HelpdeskServlet extends PwmServlet {
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
     }
 
-    private void restClientData(final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile)
-            throws IOException
-    {
-        final List<FormConfiguration> searchForm = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
-        final Map<String,String> searchColumns = new LinkedHashMap<>();
-        for (final FormConfiguration formConfiguration : searchForm) {
-            searchColumns.put(formConfiguration.getName(),formConfiguration.getLabel(pwmRequest.getLocale()));
-        }
+    private void restClientData(final PwmRequest pwmRequest, final HelpdeskBean helpdeskBean, final HelpdeskProfile helpdeskProfile)
+            throws IOException, PwmUnrecoverableException {
         final HashMap<String,Object> returnValues = new HashMap<>();
-        returnValues.put("helpdesk_search_columns",searchColumns);
+        { // search page
+            final List<FormConfiguration> searchForm = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
+            final Map<String, String> searchColumns = new LinkedHashMap<>();
+            for (final FormConfiguration formConfiguration : searchForm) {
+                searchColumns.put(formConfiguration.getName(), formConfiguration.getLabel(pwmRequest.getLocale()));
+            }
+            returnValues.put("helpdesk_search_columns", searchColumns);
+        }
+        { /// detail page
+            returnValues.put("helpdesk_setting_maskPasswords",helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_PASSWORD_MASKVALUE));
+            returnValues.put("helpdesk_setting_clearResponses",helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_CLEAR_RESPONSES,HelpdeskClearResponseMode.class));
+            returnValues.put("helpdesk_setting_PwUiMode",helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_SET_PASSWORD_MODE,HelpdeskUIMode.class));
+            returnValues.put("helpdesk_setting_tokenSendMethod",helpdeskProfile.readSettingAsEnum(PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class));
+
+
+            final HelpdeskBean.HelpdeskDetailInfo helpdeskDetailInfo = helpdeskBean.getHeldpdeskDetailInfo();
+            if (helpdeskDetailInfo != null) {
+                final UserInfoBean searchedUserInfo = helpdeskDetailInfo.getUserInfoBean();
+                if (searchedUserInfo != null && searchedUserInfo.getUserIdentity() != null) {
+                    final String obfuscatedDN = searchedUserInfo.getUserIdentity().toObfuscatedKey(pwmRequest.getConfig());
+                    returnValues.put("helpdesk_obfuscatedDN",obfuscatedDN);
+                    returnValues.put("helpdesk_username",searchedUserInfo.getUsername());
+                }
+            }
+        }
         final RestResultBean restResultBean = new RestResultBean(returnValues);
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
@@ -661,6 +681,8 @@ public class HelpdeskServlet extends PwmServlet {
                     );
                     pwmRequest.getPwmApplication().getAuditManager().submit(auditRecord);
                 }
+
+                StatisticsManager.incrementStat(pwmRequest, Statistic.HELPDESK_VERIFY_OTP);
             }
 
             // add a delay to prevent continuous checks

+ 9 - 1
pwm/servlet/src/password/pwm/http/servlet/OAuthConsumerServlet.java

@@ -40,6 +40,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.client.PwmHttpClientConfiguration;
 import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
@@ -52,6 +53,7 @@ import java.io.IOException;
 import java.io.Serializable;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
@@ -381,7 +383,13 @@ public class OAuthConsumerServlet extends PwmServlet {
         bodyEntity.setContentType(PwmConstants.ContentTypeValue.form.getHeaderValue());
         httpPost.setEntity(bodyEntity);
 
-        final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmRequest.getConfig()).execute(httpPost);
+        final X509Certificate[] certs = pwmRequest.getConfig().readSettingAsCertificate(PwmSetting.OAUTH_ID_CERTIFICATE);
+        final HttpResponse httpResponse;
+        if (certs == null || certs.length == 0) {
+            httpResponse = PwmHttpClient.getHttpClient(pwmRequest.getConfig()).execute(httpPost);
+        } else {
+            httpResponse = PwmHttpClient.getHttpClient(pwmRequest.getConfig(),new PwmHttpClientConfiguration(certs)).execute(httpPost);
+        }
         final String bodyResponse = EntityUtils.toString(httpResponse.getEntity());
 
         final StringBuilder debugOutput = new StringBuilder();

+ 6 - 1
pwm/servlet/src/password/pwm/http/servlet/ResourceFileServlet.java

@@ -131,6 +131,8 @@ public class ResourceFileServlet extends HttpServlet {
         if (files != null && !files.isEmpty()) {
             final FileValue.FileInformation fileInformation = files.keySet().iterator().next();
             final FileValue.FileContent fileContent = files.get(fileInformation);
+            LOGGER.debug("examining configured zip file resource for items name=" + fileInformation.getFilename() + ", size=" + fileContent.size());
+
             try {
                 final Map<String,FileResource> customFiles = makeMemoryFileMapFromZipInput(fileContent.getContents());
                 customFileBundle.clear();
@@ -186,7 +188,9 @@ public class ResourceFileServlet extends HttpServlet {
         PwmRequest pwmRequest = null;
         PwmApplication pwmApplication = null;
         try {
-            pwmRequest = PwmRequest.forRequest(request, response);
+            if (request.getSession(false) != null) {
+                pwmRequest = PwmRequest.forRequest(request, response);
+            }
             sessionLabel = pwmRequest.getSessionLabel();
             pwmApplication = pwmRequest.getPwmApplication();
             if (pwmAppStartupTime == null || !pwmAppStartupTime.equals(pwmApplication.getStartupTime())) {
@@ -793,6 +797,7 @@ public class ResourceFileServlet extends HttpServlet {
                 }
                 final byte[] contents = byteArrayOutputStream.toByteArray();
                 memoryMap.put(name,new MemoryFileResource(name,contents,lastModified));
+                LOGGER.trace("discovered file in configured resource bundle: " + entry.getName());
             }
         }
         return memoryMap;

+ 3 - 1
pwm/servlet/src/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -628,7 +628,9 @@ public class PeopleSearchServlet extends PwmServlet {
                 bean.setLabel(formConfiguration.getLabel(pwmRequest.getLocale()));
                 bean.setType(formConfiguration.getType());
                 if (searchAttributes.contains(formConfiguration.getName())) {
-                    bean.setSearchable(true);
+                    if (formConfiguration.getType() != FormConfiguration.Type.userDN) {
+                        bean.setSearchable(true);
+                    }
                 }
                 if (formConfiguration.getType() == FormConfiguration.Type.userDN) {
                     if (searchResults.containsKey(formConfiguration.getName())) {

+ 6 - 12
pwm/servlet/src/password/pwm/http/tag/PasswordRequirementsTag.java

@@ -264,7 +264,8 @@ public class PasswordRequirementsTag extends TagSupport {
             returnValues.add(getLocalString(Message.Requirement_ADComplexity, "", locale, config));
         } else if (ADPolicyLevel == ADPolicyComplexity.AD2008) {
             final int maxViolations = ruleHelper.readIntValue(PwmPasswordRule.ADComplexityMaxViolations);
-            returnValues.add(getLocalString(Message.Requirement_ADComplexity2008, String.valueOf(maxViolations), locale, config));
+            final int minGroups = 5 - maxViolations;
+            returnValues.add(getLocalString(Message.Requirement_ADComplexity2008, String.valueOf(minGroups), locale, config));
         }
 
         if (ruleHelper.readBooleanValue(PwmPasswordRule.UniqueRequired)) {
@@ -275,19 +276,12 @@ public class PasswordRequirementsTag extends TagSupport {
     }
 
     private static String getLocalString(final Message message, final int size, final Locale locale, final Configuration config) {
-        if (size > 1) {
-            try {
-                try {
-                    final Message pluralMessage = Message.valueOf(message.toString() + "PLURAL");
-                    return Message.getLocalizedMessage(locale, pluralMessage, config, String.valueOf(size));
-                } catch (IllegalArgumentException e) {/*noop*/ }
-            } catch (MissingResourceException e) {
-                LOGGER.error("unable to display requirement tag for message '" + message.toString() + "PLURAL': " + e.getMessage());
-            }
-        }
+        final Message effectiveMessage = size > 1 && message.getPluralMessage() != null
+                ? message.getPluralMessage()
+                : message;
 
         try {
-            return Message.getLocalizedMessage(locale, message, config, String.valueOf(size));
+            return Message.getLocalizedMessage(locale, effectiveMessage, config, String.valueOf(size));
         } catch (MissingResourceException e) {
             LOGGER.error("unable to display requirement tag for message '" + message.toString() + "': " + e.getMessage());
         }

+ 3 - 1
pwm/servlet/src/password/pwm/http/tag/PwmUrlTag.java

@@ -90,7 +90,9 @@ public class PwmUrlTag extends PwmAbstractTag {
         if (addContext) {
             workingUrl = insertContext(pageContext, workingUrl);
         }
-        workingUrl = insertResourceNonce(pwmRequest.getPwmApplication(), workingUrl);
+        if (pwmRequest != null) {
+            workingUrl = insertResourceNonce(pwmRequest.getPwmApplication(), workingUrl);
+        }
         //workingUrl = injectFormID(pwmSession, workingUrl);
         outputURL = workingUrl;
         //} catch (PwmUnrecoverableException e) {

+ 20 - 2
pwm/servlet/src/password/pwm/http/tag/PwmValueTag.java

@@ -31,6 +31,7 @@ import password.pwm.util.macro.MacroMachine;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspPage;
 import javax.servlet.jsp.JspTagException;
 import javax.servlet.jsp.tagext.TagSupport;
 
@@ -74,7 +75,7 @@ public class PwmValueTag extends TagSupport {
         return EVAL_PAGE;
     }
 
-    public static String calcValue(
+    public String calcValue(
             final PwmRequest pwmRequest,
             final VALUE value
     ) {
@@ -128,6 +129,21 @@ public class PwmValueTag extends TagSupport {
                 }
                 return "";
             }
+
+            case currentJspFilename: {
+                final JspPage jspPage = (JspPage)pageContext.getPage();
+                if (jspPage != null) {
+                    String name = jspPage.getClass().getSimpleName();
+                    name = name.replaceAll("_002d", "-");
+                    name = name.replaceAll("_", ".");
+                    return name;
+                }
+                return "";
+            }
+
+            case instanceID: {
+                return pwmRequest.getPwmApplication().getInstanceID();
+            }
         }
 
         return "";
@@ -138,7 +154,9 @@ public class PwmValueTag extends TagSupport {
         homeURL,
         passwordFieldType,
         responseFieldType,
-        customJavascript
+        customJavascript,
+        currentJspFilename,
+        instanceID,
     }
 }
 

+ 6 - 2
pwm/servlet/src/password/pwm/i18n/Admin.properties

@@ -32,8 +32,10 @@ IntruderRecordType_USER_ID=User DN
 IntruderRecordType_ATTRIBUTE=Attribute
 IntruderRecordType_TOKEN_DEST=Token Destination
 Notice_DynamicRefresh=This content is dynamically updated.
+Notice_ReportSummary=Locally cached data is shown here.  This data is periodically read from the configured LDAP directories.
+Notice_EventStatistics=Event statistics shown are for this local server.
 Button_DownloadCSV=Download as CSV
-Tooltip_DownloadReportRecords=Download all records to a Comma Separated Value (CSV) file.  <ul><li>All cached records and columns will be downloaded</li><li>Columns are comma delimited (,)</li><li>Fields are wrapped with double quotation marks (") when containing a comma (,) in the value</li><li>The first row will contain field names</li></ul>.
+Tooltip_DownloadReportRecords=Download all records to a Comma Separated Value (CSV) file.  All cached records and columns will be downloaded.  Columns are comma delimited (,)  Fields are wrapped with double quotation marks (") when containing a comma (,) in the value.  The first row will contain field names.
 Field_Report_UserDN=UserDN
 Field_Report_LDAP_Profile=LDAP Profile
 Field_Report_Username=Username
@@ -234,8 +236,10 @@ Statistic_Label.HelpdeskUserLookup=Helpdesk User Lookups
 Statistic_Description.HelpdeskUserLookup=Number of helpdesk user detail views requested by helpdesk operators.
 Statistic_Label.HelpdeskTokenSent=Helpdesk Tokens Sent
 Statistic_Description.HelpdeskTokenSent=Number of helpdesk verification tokens sent.
-Statistic_Label.HelpdeskUnlock=Heldesk Unlocks
+Statistic_Label.HelpdeskUnlock=Helpdesk Unlocks
 Statistic_Description.HelpdeskUnlock=Number of helpdesk unlock user events.
+Statistic_Label.HelpdeskVerifyOTP=Helpdesk OTP Verifications
+Statistic_Description.HelpdeskVerifyOTP=Number of successful helpdesk OPT verification user events.
 Statistic_Label.RestStatus=WebService Status Calls
 Statistic_Description.RestStatus=Number of external web service calls to the /status REST interface.
 Statistic_Label.RestCheckPassword=WebService Check Password Calls

+ 94 - 99
pwm/servlet/src/password/pwm/i18n/Config.java

@@ -28,106 +28,101 @@ import java.util.Locale;
 
 public enum Config implements PwmDisplayBundle {
 
-Button_Next,
-Button_Previous,
-Button_CheckSettings,
-Button_ShowAdvanced,
-Button_HideAdvanced,
-Confirm_LockConfig,
-Confirm_SkipGuide,
-Confirm_UploadConfig,
-Confirm_UploadLocalDB,
-Confirm_SSLDisable,
-Display_AboutTemplates,
-Display_ConfigEditorLocales,
-Display_ConfigGuideNotSecureLDAP,
-Display_ConfigGuideSelectTemplate,
-Display_ConfigGuideSelectCrStorage,
-Display_ConfigGuideLdapSchema,
-Display_ConfigGuideLdapSchema2,
-Display_ConfigManagerConfiguration,
-Display_ConfigManagerNew,
-Display_ConfigManagerRunning,
-Display_ConfigManagerRunningEditor,
-Display_ConfigOpenInfo,
-Display_SettingFilter_Level_0,
-Display_SettingFilter_Level_1,
-Display_SettingFilter_Level_2,
-Display_SettingNavigationSeparator,
-Field_VerificationMethodPreviousAuth,
-Field_VerificationMethodToken,
-Field_VerificationMethodOTP,
-Field_VerificationMethodChallengeResponses,
-Field_VerificationMethodAttributes,
-Field_VerificationMethodRemoteResponses,
-Field_VerificationMethod,
-MenuDisplay_AlternateNewConfig,
-MenuDisplay_AlternateUnlockConfig,
-MenuDisplay_AlternateUpload,
-MenuDisplay_CancelEdits,
-MenuDisplay_ConfigEditor,
-MenuDisplay_DownloadConfig,
-MenuDisplay_DownloadConfigRunning,
-MenuDisplay_DownloadBundle,
-MenuDisplay_LockConfig,
-MenuDisplay_UnlockConfig,
-MenuDisplay_ExportLocalDB,
-MenuDisplay_MainMenu,
-MenuDisplay_ManualConfig,
-MenuDisplay_ReturnToEditor,
-MenuDisplay_SaveConfig,
-MenuDisplay_CancelConfig,
-MenuDisplay_StartConfigGuide,
-MenuDisplay_UploadConfig,
-MenuDisplay_ViewLog,
-MenuItem_AlternateNewConfig,
-MenuItem_AlternateUnlockConfig,
-MenuItem_CancelEdits,
-MenuItem_DownloadConfig,
-MenuItem_DownloadBundle,
-MenuItem_LockConfig,
-MenuItem_ExportLocalDB,
-MenuItem_MainMenu,
-MenuItem_ManualConfig,
-MenuItem_ReturnToEditor,
-MenuItem_StartConfigGuide,
-MenuItem_UploadConfig,
-MenuItem_UnlockConfig,
-MenuItem_ViewLog,
-MenuItem_Home,
-Setting_Permission_Profile,
-Setting_Permission_Filter,
-Setting_Permission_Base,
-Setting_Permission_Base_Group,
-Title_ConfigGuide,
-Title_ConfigGuide_app,
-Title_ConfigGuide_ldap,
-Title_ConfigGuide_ldapcert,
-Title_ConfigGuide_ldap_schema,
-Title_ConfigGuide_start,
-Title_ConfigGuide_template,
-Title_ConfigGuide_crStorage,
-Title_ConfigManager,
-Warning_ChangeTemplate,
-Warning_ResetSetting,
-Warning_ShowAdvanced,
-Warning_ShowDescription,
-Warning_ShowNotes,
-Warning_HeaderVisibility,
-Warning_ConfigMustBeClosed,
-Warning_MakeSupportZipNoTrace,
-Warning_DownloadSupportZip,
-Warning_DownloadConfiguration,
-Warning_DownloadLocal,
-Warning_InvalidFormat,
-Warning_UploadIE9,
-Tooltip_ResetButton,
-Tooltip_HelpButton,
-Tooltip_Setting_Permission_Profile,
-Tooltip_Setting_Permission_Filter,
-Tooltip_Setting_Permission_Base,
+    Button_Next,
+    Button_Previous,
+    Button_CheckSettings,
+    Button_ShowAdvanced,
+    Button_HideAdvanced,
+    Confirm_LockConfig,
+    Confirm_SkipGuide,
+    Confirm_UploadConfig,
+    Confirm_UploadLocalDB,
+    Confirm_SSLDisable,
+    Display_AboutTemplates,
+    Display_ConfigEditorLocales,
+    Display_ConfigGuideNotSecureLDAP,
+    Display_ConfigGuideSelectTemplate,
+    Display_ConfigGuideSelectCrStorage,
+    Display_ConfigGuideLdapSchema,
+    Display_ConfigGuideLdapSchema2,
+    Display_ConfigManagerConfiguration,
+    Display_ConfigManagerNew,
+    Display_ConfigManagerRunning,
+    Display_ConfigManagerRunningEditor,
+    Display_ConfigOpenInfo,
+    Display_SettingFilter_Level_0,
+    Display_SettingFilter_Level_1,
+    Display_SettingFilter_Level_2,
+    Display_SettingNavigationSeparator,
+    Display_SettingNavigationNullProfile,
+    Field_VerificationMethod,
+    MenuDisplay_AlternateNewConfig,
+    MenuDisplay_AlternateUnlockConfig,
+    MenuDisplay_AlternateUpload,
+    MenuDisplay_CancelEdits,
+    MenuDisplay_ConfigEditor,
+    MenuDisplay_DownloadConfig,
+    MenuDisplay_DownloadConfigRunning,
+    MenuDisplay_DownloadBundle,
+    MenuDisplay_LockConfig,
+    MenuDisplay_UnlockConfig,
+    MenuDisplay_ExportLocalDB,
+    MenuDisplay_MainMenu,
+    MenuDisplay_ManualConfig,
+    MenuDisplay_ReturnToEditor,
+    MenuDisplay_SaveConfig,
+    MenuDisplay_CancelConfig,
+    MenuDisplay_StartConfigGuide,
+    MenuDisplay_UploadConfig,
+    MenuDisplay_ViewLog,
+    MenuItem_AlternateNewConfig,
+    MenuItem_AlternateUnlockConfig,
+    MenuItem_CancelEdits,
+    MenuItem_DownloadConfig,
+    MenuItem_DownloadBundle,
+    MenuItem_LockConfig,
+    MenuItem_ExportLocalDB,
+    MenuItem_MainMenu,
+    MenuItem_ManualConfig,
+    MenuItem_ReturnToEditor,
+    MenuItem_StartConfigGuide,
+    MenuItem_UploadConfig,
+    MenuItem_UnlockConfig,
+    MenuItem_ViewLog,
+    MenuItem_Home,
+    Setting_Permission_Profile,
+    Setting_Permission_Filter,
+    Setting_Permission_Base,
+    Setting_Permission_Base_Group,
+    Title_ConfigGuide,
+    Title_ConfigGuide_app,
+    Title_ConfigGuide_ldap,
+    Title_ConfigGuide_ldapcert,
+    Title_ConfigGuide_ldap_schema,
+    Title_ConfigGuide_start,
+    Title_ConfigGuide_template,
+    Title_ConfigGuide_crStorage,
+    Title_ConfigManager,
+    Warning_ChangeTemplate,
+    Warning_ResetSetting,
+    Warning_ShowAdvanced,
+    Warning_ShowDescription,
+    Warning_ShowNotes,
+    Warning_HeaderVisibility,
+    Warning_ConfigMustBeClosed,
+    Warning_MakeSupportZipNoTrace,
+    Warning_DownloadSupportZip,
+    Warning_DownloadConfiguration,
+    Warning_DownloadLocal,
+    Warning_InvalidFormat,
+    Warning_UploadIE9,
+    Tooltip_ResetButton,
+    Tooltip_HelpButton,
+    Tooltip_Setting_Permission_Profile,
+    Tooltip_Setting_Permission_Filter,
+    Tooltip_Setting_Permission_Base,
 
-;
+    ;
 
     public static String getLocalizedMessage(final Locale locale, final Config key, final Configuration config) {
         return LocaleHelper.getLocalizedMessage(locale, key.toString(), config, Config.class);

+ 4 - 7
pwm/servlet/src/password/pwm/i18n/Config.properties

@@ -49,12 +49,9 @@ Display_SettingFilter_Level_0=Required
 Display_SettingFilter_Level_1=Standard / Common
 Display_SettingFilter_Level_2=Advanced
 Display_SettingNavigationSeparator=\u0020\u21e8\u0020
-Field_VerificationMethodPreviousAuth=Previous Authentication
-Field_VerificationMethodToken=SMS/Email Token Verification
-Field_VerificationMethodOTP=OTP (Mobile Device) Verification
-Field_VerificationMethodChallengeResponses=Challenge/Response Answers
-Field_VerificationMethodAttributes=LDAP Attributes
-Field_VerificationMethodRemoteResponses=External Responses
+Display_SettingNavigationNullProfile=[profile]
+Display_RememberLogin=Remember password for %1%.
+Display_ProfileNamingRules=<p>Profile names have the following requirements\:</p><ul><li>Start with a letter (a-Z)</li><li>Contain only letters, numbers and hyphens</li><li>Length between 2 and 15 characters</li></ul>
 MenuDisplay_AlternateNewConfig=Edit a new configuration in memory by selecting a new configuration template. After editing the configuration, you can download the <em>%1%</em> file.  This option will not modify the running configuration.
 MenuDisplay_AlternateUnlockConfig=The closing of the <em>%1%</em> file is controlled by the property "configIsEditable" within the file.  Set this property to "true" to return to the online configuration mode. Be aware that while this property is set to true anyone accessing this site can make modifications to the live configuration without authentication.
 MenuDisplay_AlternateUpload=Alternatively, you may upload a previously saved configuration file. The uploaded file will be saved as the new configuration.
@@ -109,7 +106,7 @@ Warning_ShowDescription=Help text for settings is available by clicking on setti
 Warning_ShowNotes=Notes exist for this configuration.  Select <em>Configuration Notes</em> from the <em>View</em> menu to show the notes.
 Warning_HeaderVisibility=Drag to pointer to top of screen to re-enable the alert header bar.
 Warning_ConfigMustBeClosed=<p>The configuration must be closed before these settings are available.</p><p>Close the configuration using the <a href="%1%">ConfigManager</a>.</p>
-Warning_MakeSupportZipNoTrace=<p>The configuration setting <code>@PwmSettingReference:events.pwmDB.logLevel@</code> must be set to level <code>TRACE</code> before this option can be used.</p>
+Warning_MakeSupportZipNoTrace=<b>Notice:</b> The configuration setting <code>@PwmSettingReference:events.pwmDB.logLevel@</code> is not set to level <code>TRACE</code>.  The download file may not contain all the debug information desired.
 Warning_DownloadSupportZip=<b>Warning:</b> The download file contains sensitive security information, handle with appropriate care.
 Warning_DownloadConfiguration=<b>Warning:</b> The configuration download file contains sensitive security information, including security credentials, handle with appropriate care.
 Warning_DownloadLocal=<b>Warning:</b> The download LocalDB archive may contain sensitive security information, handle with appropriate care.

+ 12 - 0
pwm/servlet/src/password/pwm/i18n/ConfigEditor.java

@@ -0,0 +1,12 @@
+package password.pwm.i18n;
+
+public enum ConfigEditor implements PwmDisplayBundle {
+
+
+    ;
+
+    @Override
+    public String getKey() {
+        return this.toString();
+    }
+}

File diff suppressed because it is too large
+ 246 - 0
pwm/servlet/src/password/pwm/i18n/ConfigEditor.properties


+ 1 - 0
pwm/servlet/src/password/pwm/i18n/Display.java

@@ -222,6 +222,7 @@ public enum Display implements PwmDisplayBundle {
     Field_VerificationMethodChallengeResponses,
     Field_VerificationMethodAttributes,
     Field_VerificationMethodRemoteResponses,
+    Field_VerificationMethodNAAF,
     Field_VerificationMethod,
     Long_Title_ActivateUser,
     Long_Title_Admin,

+ 11 - 0
pwm/servlet/src/password/pwm/i18n/Display.properties

@@ -96,6 +96,16 @@ Display_LoginPasswordOnly=Please enter your password below.  Your current passwo
 Display_Logout=<b>You are now logged out.</b><p/>Please close all internet browser windows before you try to login again.
 Display_Minute=minute
 Display_Minutes=minutes
+Display_NAAF_PASSWORD=Please provide your NAAF authentication password.
+Display_NAAF_LDAP_PASSWORD=Please provide your LDAP authentication password.
+Display_NAAF_SECURITY_QUESTIONS=Please answer your security questions.
+Display_NAAF_EMAIL_OTP=An Email has been sent with your one time password.
+Display_NAAF_SMS_OTP=An SMS has been sent with your one time password.
+Display_NAAF_SMARTPHONE=The smart phone verification process has started.  Please continue when complete.
+Display_NAAF_RADIUS=Please provide your RADIUS authentication password.
+Display_NAAF_TOTP=Please enter your TOTP value.
+Display_NAAF_HOTP=Please enter your HOTP value.
+Display_NAAF_VOICE=The voice verification process has started.  Please continue when complete.
 Display_NewUser=To register a new account, please complete the following form.
 Display_NewUserProfile=To register a new account, please select a profile.
 Display_PasswordExpired=Your password has expired.  You must set a new password now.
@@ -211,6 +221,7 @@ Field_VerificationMethodOTP=Mobile Device Verification
 Field_VerificationMethodChallengeResponses=Secret Questions and Answers
 Field_VerificationMethodAttributes=Personal Data
 Field_VerificationMethodRemoteResponses=External Responses
+Field_VerificationMethodNAAF=Advanced Authentication
 Long_Title_ActivateUser=Activate a pre-configured account and establish a new password.
 Long_Title_Admin=Administrative functions
 Long_Title_ChangePassword=Change your current password.

+ 2 - 0
pwm/servlet/src/password/pwm/i18n/Error.properties

@@ -156,6 +156,8 @@ Error_MacroParseError=Macro parse error: %1%
 Error_NoProfileAssigned=No profile is assigned for this operation.
 Error_StartupError=An error occurred while starting the application.  Check the log files for information.
 
+Error_RemoteErrorValue=Remote Error: %1%
+
 Error_ConfigUploadSuccess=File uploaded successfully
 Error_ConfigUploadFailure=File failed to upload.
 Error_ConfigSaveSuccess=Configuration saved successfully.  Application restart has been requested.  The application may be unavailable while restarting.  If the restart request fails you may need to restart the application server manually.

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Health.properties

@@ -54,7 +54,7 @@ HealthMessage_Config_MissingProxyPassword=Missing proxy user password for profil
 HealthMessage_LDAP_VendorsNotSame=LDAP directories of different vendor types are in use.  This configuration may cause undesirable side effects and is not supported.  %1%
 HealthMessage_LDAP_Ad_History_Asn_Missing=%1% is enabled, but the server at %2% does not support this feature.  Check to be sure it is upgraded to Windows Server 2008 R2 SP1 or greater.  Password changes against this server may fail until this is resolved.
 HealthMessage_LDAP_RecentlyUnreachable=LDAP profile %1% was recently unavailable (%2% ago at %3%): %4%
-HealthMessage_Config_ConfigMode=Application is currently in <b>configuration</b> mode.   Anyone accessing this site can modify the configuration without a directory authentication.  When ready, lock the configuration to prevent unauthorized configuration changes.  The configuration can still be edited after closing but will required authentication first.
+HealthMessage_Config_ConfigMode=Application is currently in <b>configuration</b> mode.   Anyone accessing this site can modify the configuration without a directory authentication.  When ready, lock the configuration to prevent unauthorized configuration changes.  The configuration can still be edited after closing but will require directory authentication first.
 HealthMessage_CryptoTokenWithNewUserVerification=New User Email Verification is enabled and the token storage method is set to STORE_LDAP, this configuration is not supported.
 HealthMessage_TokenServiceError=An error occurred during the TokenService startup: %1%
 HealthMessage_Java_HighThreads=Java thread count is unusually large (%1% threads)

+ 3 - 2
pwm/servlet/src/password/pwm/i18n/LocaleHelper.java

@@ -26,6 +26,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.StoredValue;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.config.value.StringArrayValue;
@@ -344,7 +345,7 @@ public class LocaleHelper {
 
         final ConfigLocaleStats configLocaleStats = new ConfigLocaleStats();
         {
-            final StoredValue storedValue = PwmSetting.CHALLENGE_RANDOM_CHALLENGES.getDefaultValue(PwmSetting.Template.DEFAULT);
+            final StoredValue storedValue = PwmSetting.CHALLENGE_RANDOM_CHALLENGES.getDefaultValue(PwmSettingTemplate.DEFAULT);
             Map<String, List<ChallengeItemBean>> value = ((ChallengeValue) storedValue).toNativeObject();
 
             for (String localeStr : value.keySet()) {
@@ -491,7 +492,7 @@ public class LocaleHelper {
         private static List<Locale> knownLocales() {
             final List<Locale> knownLocales = new ArrayList<>();
             try {
-                final StringArrayValue stringArrayValue = (StringArrayValue) PwmSetting.KNOWN_LOCALES.getDefaultValue(PwmSetting.Template.DEFAULT);
+                final StringArrayValue stringArrayValue = (StringArrayValue) PwmSetting.KNOWN_LOCALES.getDefaultValue(PwmSettingTemplate.DEFAULT);
                 final List<String> rawValues = stringArrayValue.toNativeObject();
                 final Map<String,String> localeFlagMap = StringUtil.convertStringListToNameValuePair(rawValues, "::");
                 for (final String rawValue : localeFlagMap.keySet()) {

+ 125 - 99
pwm/servlet/src/password/pwm/i18n/Message.java

@@ -35,114 +35,140 @@ import java.util.Locale;
  * @author Jason D. Rivard
  */
 public enum Message implements PwmDisplayBundle {
-    Success_PasswordChange(),
-    Success_SetupResponse(),
-    Success_ClearResponse(),
-    Success_Unknown(),
-    Success_CreateUser(),
-    Success_NewUserForm(),
-    Success_UpdateForm(),
-    Success_CreateGuest(),
-    Success_UpdateGuest(),
-    Success_ActivateUser(),
-    Success_UpdateProfile(),
-    Success_ResponsesMeetRules(),
-    Success_UnlockAccount(),
-    Success_ForgottenUsername(),
-    Success_ConfigFileUpload(),
-    Success_PasswordReset(),
-    Success_PasswordSend(),
-    Success_Action(),
-    Success_OtpSetup(),
-
-    EventLog_Startup(),
-    EventLog_Shutdown(),
-    EventLog_FatalEvent(),
-    EventLog_ModifyConfiguration(),
-    EventLog_IntruderAttempt(),
-    EventLog_IntruderLockout(),
-
-    EventLog_Authenticate(),
-    EventLog_AgreementPassed(),
-    EventLog_ChangePassword(),
-    EventLog_UnlockPassword(),
-    EventLog_RecoverPassword(),
-    EventLog_SetupResponses(),
-    Eventlog_SetupOtpSecret(),
-    EventLog_ActivateUser(),
-    EventLog_CreateUser(),
-    EventLog_UpdateProfile(),
-    EventLog_IntruderUser(),
-    EventLog_TokenIssued(),
-    EventLog_TokenClaimed(),
-    EventLog_ClearResponses(),
-    EventLog_HelpdeskSetPassword(),
-    EventLog_HelpdeskUnlockPassword(),
-    EventLog_HelpdeskClearResponses(),
-    EventLog_HelpdeskClearOtpSecret(),
-    EventLog_HelpdeskAction(),
-    EventLog_HelpdeskDeleteUser(),
-    EventLog_HelpdeskViewDetail(),
-    EventLog_HelpdeskVerifyOtp(),
-
-    Requirement_MinLength(),
-    Requirement_MinLengthPlural(),
-    Requirement_MaxLength(),
-    Requirement_MaxLengthPlural(),
-    Requirement_MinAlpha(),
-    Requirement_MinAlphaPlural(),
-    Requirement_MaxAlpha(),
-    Requirement_MaxAlphaPlural(),
-    Requirement_AllowNumeric(),
-    Requirement_MinNumeric(),
-    Requirement_MinNumericPlural(),
-    Requirement_MaxNumeric(),
-    Requirement_MaxNumericPlural(),
-    Requirement_FirstNumeric(),
-    Requirement_LastNumeric(),
-    Requirement_AllowSpecial(),
-    Requirement_MinSpecial(),
-    Requirement_MinSpecialPlural(),
-    Requirement_MaxSpecial(),
-    Requirement_MaxSpecialPlural(),
-    Requirement_FirstSpecial(),
-    Requirement_LastSpecial(),
-    Requirement_MaxRepeat(),
-    Requirement_MaxRepeatPlural(),
-    Requirement_MaxSeqRepeat(),
-    Requirement_MaxSeqRepeatPlural(),
-    Requirement_MinLower(),
-    Requirement_MinLowerPlural(),
-    Requirement_MaxLower(),
-    Requirement_MaxLowerPlural(),
-    Requirement_MinUpper(),
-    Requirement_MinUpperPlural(),
-    Requirement_MaxUpper(),
-    Requirement_MaxUpperPlural(),
-    Requirement_MinUnique(),
-    Requirement_MinUniquePlural(),
-    Requirement_RequiredChars(),
-    Requirement_DisAllowedValues(),
-    Requirement_DisAllowedAttributes(),
-    Requirement_WordList(),
-    Requirement_OldChar(),
-    Requirement_OldCharPlural(),
-    Requirement_CaseSensitive(),
-    Requirement_NotCaseSensitive(),
-    Requirement_MinimumFrequency(),
-    Requirement_ADComplexity(),
-    Requirement_ADComplexity2008(),
-    Requirement_UniqueRequired(),
+    Success_PasswordChange(null),
+    Success_SetupResponse(null),
+    Success_ClearResponse(null),
+    Success_Unknown(null),
+    Success_CreateUser(null),
+    Success_NewUserForm(null),
+    Success_UpdateForm(null),
+    Success_CreateGuest(null),
+    Success_UpdateGuest(null),
+    Success_ActivateUser(null),
+    Success_UpdateProfile(null),
+    Success_ResponsesMeetRules(null),
+    Success_UnlockAccount(null),
+    Success_ForgottenUsername(null),
+    Success_ConfigFileUpload(null),
+    Success_PasswordReset(null),
+    Success_PasswordSend(null),
+    Success_Action(null),
+    Success_OtpSetup(null),
+
+    EventLog_Startup(null),
+    EventLog_Shutdown(null),
+    EventLog_FatalEvent(null),
+    EventLog_ModifyConfiguration(null),
+    EventLog_IntruderAttempt(null),
+    EventLog_IntruderLockout(null),
+
+    EventLog_Authenticate(null),
+    EventLog_AgreementPassed(null),
+    EventLog_ChangePassword(null),
+    EventLog_UnlockPassword(null),
+    EventLog_RecoverPassword(null),
+    EventLog_SetupResponses(null),
+    Eventlog_SetupOtpSecret(null),
+    EventLog_ActivateUser(null),
+    EventLog_CreateUser(null),
+    EventLog_UpdateProfile(null),
+    EventLog_IntruderUser(null),
+    EventLog_TokenIssued(null),
+    EventLog_TokenClaimed(null),
+    EventLog_ClearResponses(null),
+    EventLog_HelpdeskSetPassword(null),
+    EventLog_HelpdeskUnlockPassword(null),
+    EventLog_HelpdeskClearResponses(null),
+    EventLog_HelpdeskClearOtpSecret(null),
+    EventLog_HelpdeskAction(null),
+    EventLog_HelpdeskDeleteUser(null),
+    EventLog_HelpdeskViewDetail(null),
+    EventLog_HelpdeskVerifyOtp(null),
+
+    Requirement_MinLengthPlural(null),
+    Requirement_MinLength(Requirement_MinLengthPlural),
+
+    Requirement_MaxLengthPlural(null),
+    Requirement_MaxLength(Requirement_MaxLengthPlural),
+
+    Requirement_MinAlphaPlural(null),
+    Requirement_MinAlpha(Requirement_MinAlphaPlural),
+
+    Requirement_MaxAlphaPlural(null),
+    Requirement_MaxAlpha(Requirement_MaxAlphaPlural),
+
+    Requirement_AllowNumeric(null),
+
+    Requirement_MinNumericPlural(null),
+    Requirement_MinNumeric(Requirement_MinNumericPlural),
+
+    Requirement_MaxNumericPlural(null),
+    Requirement_MaxNumeric(Requirement_MaxNumericPlural),
+
+    Requirement_FirstNumeric(null),
+    Requirement_LastNumeric(null),
+    Requirement_AllowSpecial(null),
+
+    Requirement_MinSpecialPlural(null),
+    Requirement_MinSpecial(Requirement_MinSpecialPlural),
+
+    Requirement_MaxSpecialPlural(null),
+    Requirement_MaxSpecial(Requirement_MaxSpecialPlural),
+
+    Requirement_LastSpecial(null),
+    Requirement_FirstSpecial(null),
+
+    Requirement_MaxRepeatPlural(null),
+    Requirement_MaxRepeat(Requirement_MaxRepeatPlural),
+
+    Requirement_MaxSeqRepeatPlural(null),
+    Requirement_MaxSeqRepeat(Requirement_MaxSeqRepeatPlural),
+
+    Requirement_MinLowerPlural(null),
+    Requirement_MinLower(Requirement_MinLowerPlural),
+
+    Requirement_MaxLowerPlural(null),
+    Requirement_MaxLower(Requirement_MaxLowerPlural),
+
+    Requirement_MinUpperPlural(null),
+    Requirement_MinUpper(Requirement_MinUpperPlural),
+
+    Requirement_MaxUpperPlural(null),
+    Requirement_MaxUpper(Requirement_MaxUpperPlural),
+
+    Requirement_MinUniquePlural(null),
+    Requirement_MinUnique(Requirement_MinUniquePlural),
+
+    Requirement_RequiredChars(null),
+    Requirement_DisAllowedValues(null),
+    Requirement_DisAllowedAttributes(null),
+    Requirement_WordList(null),
+
+    Requirement_OldCharPlural(null),
+    Requirement_OldChar(Requirement_OldCharPlural),
+
+    Requirement_CaseSensitive(null),
+    Requirement_NotCaseSensitive(null),
+    Requirement_MinimumFrequency(null),
+    Requirement_ADComplexity(null),
+    Requirement_ADComplexity2008(null),
+    Requirement_UniqueRequired(null),
 
     ;
 
+    private final Message pluralMessage;
+
     public static String getLocalizedMessage(final Locale locale, final Message message, final Configuration config, final String... fieldValue) {
         return LocaleHelper.getLocalizedMessage(locale, message.getKey(),config , Message.class, fieldValue);
     }
 
-    Message() {
+    Message(final Message pluralMessage) {
+        this.pluralMessage = pluralMessage;
     }
 
+    public Message getPluralMessage() {
+        return pluralMessage;
+    }
 
     public String getLocalizedMessage(final Locale locale, final Configuration config, final String... fieldValue) {
         return Message.getLocalizedMessage(locale, this, config, fieldValue);

+ 1 - 1
pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java

@@ -372,7 +372,7 @@ public class LdapOperationsHelper {
 
         final X509Certificate[] ldapServerCerts = ldapProfile.readSettingAsCertificate(PwmSetting.LDAP_SERVER_CERTS);
         if (ldapServerCerts != null && ldapServerCerts.length > 0) {
-            final X509TrustManager tm = new X509Utils.PwmTrustManager(ldapServerCerts);
+            final X509TrustManager tm = new X509Utils.CertMatchingTrustManager(config, ldapServerCerts);
             chaiConfig.setTrustManager(new X509TrustManager[]{tm});
         }
 

+ 5 - 6
pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java

@@ -317,7 +317,7 @@ public class UserSearchEngine {
 
             if (searchConfiguration.isEnableContextValidation()) {
                 for (final String searchContext : searchContexts) {
-                    validateSpecifiedContext(pwmApplication, ldapProfile, searchContext);
+                    validateSpecifiedContext(ldapProfile, searchContext);
                 }
             }
         } else {
@@ -326,7 +326,7 @@ public class UserSearchEngine {
 
         final long timeLimitMS = searchConfiguration.getSearchTimeout() != 0
                 ? searchConfiguration.getSearchTimeout()
-                : Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_SEARCH_TIMEOUT));
+                : (ldapProfile.readSettingAsLong(PwmSetting.LDAP_SEARCH_TIMEOUT) * 1000);
 
 
         final ChaiProvider chaiProvider = searchConfiguration.getChaiProvider() == null ?
@@ -408,13 +408,13 @@ public class UserSearchEngine {
         return returnMap;
     }
 
-    private static void validateSpecifiedContext(final PwmApplication pwmApplication, final LdapProfile profile, final String context)
+    private static void validateSpecifiedContext(final LdapProfile profile, final String context)
             throws PwmOperationalException
     {
-        final Collection<String> loginContexts = profile.getLoginContexts().keySet();
-        if (loginContexts == null || loginContexts.isEmpty()) {
+        if (profile.getLoginContexts() == null || profile.getLoginContexts().isEmpty()) {
             throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"context specified, but no selectable contexts are configured");
         }
+        final Collection<String> loginContexts = profile.getLoginContexts().keySet();
 
         for (final String loopContext : loginContexts) {
             if (loopContext.equals(context)) {
@@ -567,7 +567,6 @@ public class UserSearchEngine {
                 final Map<String,String> headerAttributeMap
         )
         {
-            final Date startTime = new Date();
             if (headerAttributeMap == null || headerAttributeMap.isEmpty() || results == null) {
                 return results;
             }

+ 2 - 1
pwm/servlet/src/password/pwm/util/AbstractUrlShortener.java

@@ -23,6 +23,7 @@
 package password.pwm.util;
 
 import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
 
 import java.util.Properties;
 
@@ -38,5 +39,5 @@ public interface AbstractUrlShortener {
 	 *
 	 * @param context		the PwmApplication, used to retrieve configuration
 	 */
-	public String shorten(String input, PwmApplication context);
+	public String shorten(String input, PwmApplication context) throws PwmUnrecoverableException;
 }

+ 2 - 1
pwm/servlet/src/password/pwm/util/BasicUrlShortener.java

@@ -23,6 +23,7 @@
 package password.pwm.util;
 
 import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
 
 import java.util.Properties;
 
@@ -44,7 +45,7 @@ public class BasicUrlShortener implements AbstractUrlShortener {
 		return configuration;
 	}
 	
-	public String shorten(String input, PwmApplication context) {
+	public String shorten(String input, PwmApplication context) throws PwmUnrecoverableException {
 		/* 
 		 * This function does nothing.
 		 * Real functionality has to be implemented by extending this class

+ 76 - 2
pwm/servlet/src/password/pwm/util/BuildChecksumMaker.java

@@ -25,8 +25,7 @@ package password.pwm.util;
 import password.pwm.error.PwmUnrecoverableException;
 
 import java.io.*;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.*;
 
 public class BuildChecksumMaker {
 // ------------------------------ FIELDS ------------------------------
@@ -101,6 +100,30 @@ public class BuildChecksumMaker {
         return results;
     }
 
+    public static List<FileInformation> readFileInformation(final File rootFile)
+            throws PwmUnrecoverableException, IOException
+    {
+        return readFileInformation(rootFile, "");
+    }
+
+    protected static List<FileInformation>  readFileInformation(
+            final File rootFile,
+            final String relativePath
+    )
+            throws PwmUnrecoverableException, IOException
+    {
+        final ArrayList<FileInformation> results = new ArrayList<>();
+        for (final File loopFile : rootFile.listFiles()) {
+            final String path = relativePath + loopFile.getName();
+            if (loopFile.isDirectory()) {
+                results.addAll(readFileInformation(loopFile, path + "/"));
+            } else {
+                results.add(fileInformationForFile(loopFile));
+            }
+        }
+        return results;
+    }
+
     private static String md5sumFile(final File file)
             throws PwmUnrecoverableException, FileNotFoundException
     {
@@ -111,4 +134,55 @@ public class BuildChecksumMaker {
     private static void output(final String output) {
         System.out.println(output);
     }
+
+    public static FileInformation fileInformationForFile(final File file)
+            throws IOException, PwmUnrecoverableException
+    {
+        if (file == null || !file.exists()) {
+            return null;
+        }
+        return new FileInformation(
+                file.getName(),
+                file.getAbsolutePath(),
+                new Date(file.lastModified()),
+                file.length(),
+                SecureHelper.hash(file, SecureHelper.HashAlgorithm.SHA1)
+        );
+    }
+
+    public static class FileInformation implements Serializable {
+        private final String filename;
+        private final String filepath;
+        private final Date modified;
+        private final long size;
+        private final String sha1sum;
+
+        public FileInformation(String filename, String filepath, Date modified, long size, String sha1sum) {
+            this.filename = filename;
+            this.filepath = filepath;
+            this.modified = modified;
+            this.size = size;
+            this.sha1sum = sha1sum;
+        }
+
+        public String getFilename() {
+            return filename;
+        }
+
+        public String getFilepath() {
+            return filepath;
+        }
+
+        public Date getModified() {
+            return modified;
+        }
+
+        public long getSize() {
+            return size;
+        }
+
+        public String getSha1sum() {
+            return sha1sum;
+        }
+    }
 }

+ 3 - 2
pwm/servlet/src/password/pwm/util/CodeIntegrityChecker.java

@@ -26,6 +26,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingTemplate;
 import password.pwm.error.PwmError;
 import password.pwm.health.HealthMessage;
 import password.pwm.i18n.Message;
@@ -46,8 +47,8 @@ public class CodeIntegrityChecker {
             CHECK_ENUM_METHODS.put(PwmSetting.class.getMethod("getDescription", Locale.class), new Object[]{
                     PwmConstants.DEFAULT_LOCALE
             });
-            CHECK_ENUM_METHODS.put(PwmSetting.class.getMethod("getDefaultValue", PwmSetting.Template.class), new Object[]{
-                    PwmSetting.Template.DEFAULT
+            CHECK_ENUM_METHODS.put(PwmSetting.class.getMethod("getDefaultValue", PwmSettingTemplate.class), new Object[]{
+                    PwmSettingTemplate.DEFAULT
             });
 
 

+ 100 - 3
pwm/servlet/src/password/pwm/util/Helper.java

@@ -26,6 +26,7 @@ import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.apache.commons.csv.CSVPrinter;
+import password.pwm.PwmAboutProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
@@ -39,6 +40,8 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.ConfigurationChecker;
 import password.pwm.http.ContextManager;
 import password.pwm.http.PwmSession;
+import password.pwm.i18n.Display;
+import password.pwm.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 
@@ -520,10 +523,104 @@ public class
         }
         return false;
     }
-    
-    public static CSVPrinter makeCsvPrinter(final OutputStream outputStream) 
-            throws IOException 
+
+    public static CSVPrinter makeCsvPrinter(final OutputStream outputStream)
+            throws IOException
     {
         return new CSVPrinter(new OutputStreamWriter(outputStream,PwmConstants.DEFAULT_CHARSET), PwmConstants.DEFAULT_CSV_FORMAT);
     }
+
+    private static String dateFormatForInfoBean(final Date date) {
+        if (date != null) {
+            return PwmConstants.DEFAULT_DATETIME_FORMAT.format(date);
+        } else {
+            return LocaleHelper.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE, Display.Value_NotApplicable, null);
+        }
+
+    }
+
+    public static Map<PwmAboutProperty,String> makeInfoBean(
+            final PwmApplication pwmApplication
+    ) {
+        final Map<PwmAboutProperty,String> aboutMap = new TreeMap<>();
+
+        // about page
+        aboutMap.put(PwmAboutProperty.app_version,                  PwmConstants.SERVLET_VERSION);
+        aboutMap.put(PwmAboutProperty.app_currentTime,              dateFormatForInfoBean(new Date()));
+        aboutMap.put(PwmAboutProperty.app_startTime,                dateFormatForInfoBean(pwmApplication.getStartupTime()));
+        aboutMap.put(PwmAboutProperty.app_installTime,              dateFormatForInfoBean(pwmApplication.getInstallTime()));
+        aboutMap.put(PwmAboutProperty.app_siteUrl,                  pwmApplication.getConfig().readSettingAsString(PwmSetting.PWM_SITE_URL));
+        aboutMap.put(PwmAboutProperty.app_instanceID,               pwmApplication.getInstanceID());
+        aboutMap.put(PwmAboutProperty.app_chaiApiVersion,           PwmConstants.CHAI_API_VERSION);
+
+        if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.VERSION_CHECK_ENABLE)) {
+            if (pwmApplication.getVersionChecker() != null) {
+                aboutMap.put(PwmAboutProperty.app_currentPublishedVersion, pwmApplication.getVersionChecker().currentVersion());
+                aboutMap.put(PwmAboutProperty.app_currentPublishedVersionCheckTime, dateFormatForInfoBean(pwmApplication.getVersionChecker().lastReadTimestamp()));
+            }
+        }
+
+        aboutMap.put(PwmAboutProperty.app_wordlistSize,             Integer.toString(pwmApplication.getWordlistManager().size()));
+        aboutMap.put(PwmAboutProperty.app_seedlistSize,             Integer.toString(pwmApplication.getSeedlistManager().size()));
+        if (pwmApplication.getSharedHistoryManager() != null) {
+            aboutMap.put(PwmAboutProperty.app_sharedHistorySize,    Integer.toString(pwmApplication.getSharedHistoryManager().size()));
+            aboutMap.put(PwmAboutProperty.app_sharedHistoryOldestTime, dateFormatForInfoBean(pwmApplication.getSharedHistoryManager().getOldestEntryTime()));
+        }
+
+
+        if (pwmApplication.getEmailQueue() != null) {
+            aboutMap.put(PwmAboutProperty.app_emailQueueSize,       Integer.toString(pwmApplication.getEmailQueue().queueSize()));
+            aboutMap.put(PwmAboutProperty.app_emailQueueOldestTime, dateFormatForInfoBean(pwmApplication.getEmailQueue().eldestItem()));
+        }
+
+        if (pwmApplication.getSmsQueue() != null) {
+            aboutMap.put(PwmAboutProperty.app_smsQueueSize,         Integer.toString(pwmApplication.getSmsQueue().queueSize()));
+            aboutMap.put(PwmAboutProperty.app_smsQueueOldestTime,   dateFormatForInfoBean(pwmApplication.getSmsQueue().eldestItem()));
+        }
+
+        if (pwmApplication.getAuditManager() != null) {
+            aboutMap.put(PwmAboutProperty.app_syslogQueueSize,      Integer.toString(pwmApplication.getAuditManager().syslogQueueSize()));
+        }
+
+        if (pwmApplication.getLocalDB() != null) {
+            aboutMap.put(PwmAboutProperty.app_localDbLogSize,       Integer.toString(pwmApplication.getLocalDBLogger().getStoredEventCount()));
+            aboutMap.put(PwmAboutProperty.app_localDbLogOldestTime, dateFormatForInfoBean(pwmApplication.getLocalDBLogger().getTailDate()));
+
+            aboutMap.put(PwmAboutProperty.app_localDbStorageSize,   formatDiskSize(getFileDirectorySize(pwmApplication.getLocalDB().getFileLocation())));
+            aboutMap.put(PwmAboutProperty.app_localDbFreeSpace,     formatDiskSize(diskSpaceRemaining(pwmApplication.getLocalDB().getFileLocation())));
+        }
+
+
+        { // java info
+            final Runtime runtime = Runtime.getRuntime();
+            aboutMap.put(PwmAboutProperty.java_memoryFree,          Long.toString(runtime.freeMemory()));
+            aboutMap.put(PwmAboutProperty.java_memoryAllocated,     Long.toString(runtime.totalMemory()));
+            aboutMap.put(PwmAboutProperty.java_memoryMax,           Long.toString(runtime.maxMemory()));
+            aboutMap.put(PwmAboutProperty.java_threadCount,         Integer.toString(Thread.activeCount()));
+
+            aboutMap.put(PwmAboutProperty.java_vmVendor,            System.getProperty("java.vm.vendor"));
+
+            aboutMap.put(PwmAboutProperty.java_runtimeVersion,      System.getProperty("java.runtime.version"));
+            aboutMap.put(PwmAboutProperty.java_vmVersion,           System.getProperty("java.vm.version"));
+            aboutMap.put(PwmAboutProperty.java_vmName,              System.getProperty("java.vm.name"));
+            aboutMap.put(PwmAboutProperty.java_vmLocation,          System.getProperty("java.home"));
+
+            aboutMap.put(PwmAboutProperty.java_osName,              System.getProperty("os.name"));
+            aboutMap.put(PwmAboutProperty.java_osVersion,           System.getProperty("os.version"));
+            aboutMap.put(PwmAboutProperty.java_randomAlgorithm,     PwmRandom.getInstance().getAlgorithm());
+        }
+
+        { // build info
+            aboutMap.put(PwmAboutProperty.build_Time,               PwmConstants.BUILD_TIME);
+            aboutMap.put(PwmAboutProperty.build_Number,             PwmConstants.BUILD_NUMBER);
+            aboutMap.put(PwmAboutProperty.build_Type,               PwmConstants.BUILD_TYPE);
+            aboutMap.put(PwmAboutProperty.build_User,               PwmConstants.BUILD_USER);
+            aboutMap.put(PwmAboutProperty.build_Revision,           PwmConstants.BUILD_REVISION);
+            aboutMap.put(PwmAboutProperty.build_JavaVendor,         PwmConstants.BUILD_JAVA_VENDOR);
+            aboutMap.put(PwmAboutProperty.build_JavaVersion,        PwmConstants.BUILD_JAVA_VERSION);
+            aboutMap.put(PwmAboutProperty.build_Version,            PwmConstants.BUILD_VERSION);
+        }
+
+        return Collections.unmodifiableMap(aboutMap);
+    }
 }

+ 5 - 0
pwm/servlet/src/password/pwm/util/JsonUtil.java

@@ -90,6 +90,11 @@ public class JsonUtil {
         }.getType());
     }
 
+    public static Map<String, Object> deserializeMap(final String jsonString) {
+        return JsonUtil.getGson().fromJson(jsonString, new TypeToken<Map<String, Object>>() {
+        }.getType());
+    }
+
     public static <T> T deserialize(final String json, final Class<T> classOfT) {
         return JsonUtil.getGson().fromJson(json, classOfT);
     }

+ 1 - 1
pwm/servlet/src/password/pwm/util/PwmPasswordRuleValidator.java

@@ -457,7 +457,7 @@ public class PwmPasswordRuleValidator {
                 break;
 
             case AD2003:
-                if (complexityPoints < 3) {
+                if (complexityPoints >= 3) {
                     return errorList;
                 }
                 break;

+ 39 - 1
pwm/servlet/src/password/pwm/util/SecureHelper.java

@@ -100,9 +100,20 @@ public class SecureHelper {
             final boolean urlSafe
     )
             throws PwmUnrecoverableException
+    {
+        return encryptToString(value, key, urlSafe, DEFAULT_BLOCK_ALGORITHM);
+    }
+
+    public static String encryptToString(
+            final String value,
+            final SecretKey key,
+            final boolean urlSafe,
+            final BlockAlgorithm blockAlgorithm
+    )
+            throws PwmUnrecoverableException
     {
         try {
-            final byte[] encrypted = encryptToBytes(value, key);
+            final byte[] encrypted = encryptToBytes(value, key, blockAlgorithm);
             return urlSafe
                     ? StringUtil.base64Encode(encrypted, StringUtil.Base64Options.URL_SAFE, StringUtil.Base64Options.GZIP)
                     : StringUtil.base64Encode(encrypted);
@@ -278,6 +289,33 @@ public class SecureHelper {
         return hash(new ByteArrayInputStream(input), algorithm);
     }
 
+    public static String hash(
+            final File file
+    )
+            throws IOException, PwmUnrecoverableException
+    {
+        return hash(file,DEFAULT_HASH_ALGORITHM);
+    }
+    public static String hash(
+            final File file,
+            final HashAlgorithm hashAlgorithm
+    )
+            throws IOException, PwmUnrecoverableException
+    {
+        if (file == null || !file.exists()) {
+            return null;
+        }
+        FileInputStream fileInputStream = null;
+        try {
+            fileInputStream = new FileInputStream(file);
+            return hash(fileInputStream, hashAlgorithm);
+        } finally {
+            if (fileInputStream != null) {
+                fileInputStream.close();
+            }
+        }
+    }
+
     public static String hash(
             final String input
     )

+ 4 - 7
pwm/servlet/src/password/pwm/util/ServletHelper.java

@@ -35,10 +35,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.ContextManager;
-import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmResponse;
-import password.pwm.http.PwmSession;
+import password.pwm.http.*;
 import password.pwm.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.stats.Statistic;
@@ -345,6 +342,7 @@ public class ServletHelper {
             throws PwmUnrecoverableException
     {
         final SessionStateBean ssBean = pwmSession.getSessionStateBean();
+        final PwmURL pwmURL = pwmRequest.getURL();
 
         // mark if first request
         if (ssBean.getSessionCreationTime() == null) {
@@ -363,7 +361,7 @@ public class ServletHelper {
         }
 
         // update the privateUrlAccessed flag
-        if (pwmRequest.getURL().isPrivateUrl()) {
+        if (pwmURL.isPrivateUrl()) {
             ssBean.setPrivateUrlAccessed(true);
         }
 
@@ -373,7 +371,7 @@ public class ServletHelper {
         }
 
         // set idle timeout (may get overridden by module-specific values elsewhere
-        {
+        if (!pwmURL.isResourceURL() && !pwmURL.isCommandServletURL() && !pwmURL.isWebServiceURL()){
             final int sessionIdleSeconds = (int) pwmApplication.getConfig().readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
             pwmSession.setSessionTimeout(pwmRequest.getHttpServletRequest().getSession(), sessionIdleSeconds);
         }
@@ -538,5 +536,4 @@ public class ServletHelper {
 
         return output.toString();
     }
-
 }

+ 9 - 0
pwm/servlet/src/password/pwm/util/StringUtil.java

@@ -252,4 +252,13 @@ public abstract class StringUtil {
 
         return sb.toString();
     }
+
+    public static Collection<String> whitespaceSplit(final String input) {
+        if (input == null) {
+            return Collections.emptyList();
+        }
+
+        final String[] splitValues = input.trim().split("\\s+");
+        return Arrays.asList(splitValues);
+    }
 }

+ 11 - 5
pwm/servlet/src/password/pwm/util/TimeDuration.java

@@ -286,27 +286,33 @@ public class TimeDuration implements Comparable, Serializable {
             sb.append(timeDetail.days);
             sb.append(" ");
             sb.append(timeDetail.days == 1 ? LocaleHelper.getLocalizedMessage(locale, Display.Display_Day, null) : LocaleHelper.getLocalizedMessage(locale,Display.Display_Days,null));
-            sb.append(", ");
         }
 
         //output number of hours
         if (timeDetail.hours > 0) {
+            if (sb.length() > 0) {
+                sb.append(", ");
+            }
             sb.append(timeDetail.hours);
             sb.append(" ");
             sb.append(timeDetail.hours == 1 ? LocaleHelper.getLocalizedMessage(locale,Display.Display_Hour,null) : LocaleHelper.getLocalizedMessage(locale,Display.Display_Hours,null));
-            sb.append(", ");
         }
 
         //output number of minutes
-        if (timeDetail.days > 0 || timeDetail.hours > 0 || timeDetail.minutes > 0) {
+        if (timeDetail.minutes > 0) {
+            if (sb.length() > 0) {
+                sb.append(", ");
+            }
             sb.append(timeDetail.minutes);
             sb.append(" ");
             sb.append(timeDetail.minutes == 1 ? LocaleHelper.getLocalizedMessage(locale,Display.Display_Minute,null) : LocaleHelper.getLocalizedMessage(locale,Display.Display_Minutes,null));
-            sb.append(", ");
         }
 
         //seconds
-        {
+        if (timeDetail.seconds > 0 || sb.toString().isEmpty()) {
+            if (sb.length() > 0) {
+                sb.append(", ");
+            }
             sb.append(timeDetail.seconds);
             sb.append(" ");
             sb.append(timeDetail.seconds == 1 ? LocaleHelper.getLocalizedMessage(locale,Display.Display_Second,null) : LocaleHelper.getLocalizedMessage(locale,Display.Display_Seconds,null));

+ 3 - 2
pwm/servlet/src/password/pwm/util/TinyUrlShortener.java

@@ -27,6 +27,7 @@ import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.util.EntityUtils;
 import password.pwm.PwmApplication;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.util.logging.PwmLogger;
 
@@ -44,7 +45,7 @@ public class TinyUrlShortener extends BasicUrlShortener {
 		this.configuration = configuration;
 	}
 	
-	public String shorten(String input, PwmApplication context) {
+	public String shorten(String input, PwmApplication context) throws PwmUnrecoverableException {
 		try {
 			LOGGER.debug("Trying to shorten url: "+input);
 			final String encodedUrl = StringUtil.urlEncode(input);
@@ -61,7 +62,7 @@ public class TinyUrlShortener extends BasicUrlShortener {
     	    	LOGGER.error("Failed to get shorter URL: "+httpResponse.getStatusLine().getReasonPhrase());
     	    }
 		} catch (java.io.IOException e) {
-			LOGGER.error("IOException: "+e.getMessage());
+			LOGGER.error("IOException: " + e.getMessage());
 		}
 		return input;
 	}

+ 3 - 3
pwm/servlet/src/password/pwm/util/UrlShortenerService.java

@@ -103,14 +103,14 @@ public class UrlShortenerService implements PwmService {
     }
 
 // -------------------------- OTHER METHODS --------------------------
-    public String shortenUrl(String text) {
+    public String shortenUrl(String text) throws PwmUnrecoverableException {
         if (theShortener != null) {
             return theShortener.shorten(text, pwmApplication);
         }
         return text;
     }
     
-    public String shortenUrlInText(String text) {
+    public String shortenUrlInText(String text) throws PwmUnrecoverableException {
         final String urlRegex = pwmApplication.getConfig().readAppProperty(AppProperty.URL_SHORTNER_URL_REGEX);
         try {
             final Pattern p = Pattern.compile(urlRegex);
@@ -138,7 +138,7 @@ public class UrlShortenerService implements PwmService {
                 return result;
             }
         } catch (PatternSyntaxException e) {
-            LOGGER.error("Error compiling pattern: "+e.getMessage());
+            LOGGER.error("Error compiling pattern: " + e.getMessage());
         }
         return text;
     }

+ 77 - 23
pwm/servlet/src/password/pwm/util/X509Utils.java

@@ -22,6 +22,8 @@
 
 package password.pwm.util;
 
+import password.pwm.AppProperty;
+import password.pwm.config.Configuration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -39,35 +41,52 @@ import java.security.cert.X509Certificate;
 public abstract class X509Utils {
     private static final PwmLogger LOGGER = PwmLogger.forClass(X509Utils.class);
 
-    public static X509Certificate[] readLdapServerCerts(final URI ldapUri)
+    public static X509Certificate[] readRemoteCertificates(final URI uri)
             throws PwmOperationalException
     {
-        final String ldapHost = ldapUri.getHost();
-        final int ldapPort = ldapUri.getPort();
-        return readLdapServerCerts(ldapHost, ldapPort);
+        final String host = uri.getHost();
+        final int port = uri.getPort() > -1
+                ? uri.getPort()
+                : portForUriScheme(uri.getScheme());
+
+        return readRemoteCertificates(host, port);
+    }
+
+    private static int portForUriScheme(final String scheme) {
+        if (scheme == null) {
+            throw new NullPointerException("scheme cannot be null");
+        }
+        switch (scheme) {
+            case "http": return 80;
+            case "https": return 443;
+            case "ldap": return 389;
+            case "ldaps": return 636;
+        }
+        throw new IllegalArgumentException("unknown scheme: " + scheme);
     }
 
-    public static X509Certificate[] readLdapServerCerts(final String ldapHost, final int ldapPort)
+
+    public static X509Certificate[] readRemoteCertificates(final String host, final int port)
             throws PwmOperationalException
     {
-        LOGGER.debug("ServerCertReader: beginning certificate read procedure to import ldap certificates from host=" + ldapHost + ", port=" + ldapPort);
+        LOGGER.debug("ServerCertReader: beginning certificate read procedure to import certificates from host=" + host + ", port=" + port);
         final CertReaderTrustManager certReaderTm = new CertReaderTrustManager();
         try { // use custom trust manager to read the certificates
             final SSLContext ctx = SSLContext.getInstance("TLS");
             ctx.init(null, new TrustManager[]{certReaderTm}, new SecureRandom());
             final SSLSocketFactory factory = ctx.getSocketFactory();
-            final SSLSocket sslSock = (SSLSocket) factory.createSocket(ldapHost,ldapPort);
-            LOGGER.debug("ServerCertReader: socket established to host=" + ldapHost + ", port=" + ldapPort);
+            final SSLSocket sslSock = (SSLSocket) factory.createSocket(host,port);
+            LOGGER.debug("ServerCertReader: socket established to host=" + host + ", port=" + port);
             sslSock.isConnected();
-            LOGGER.debug("ServerCertReader: connected to host=" + ldapHost + ", port=" + ldapPort);
+            LOGGER.debug("ServerCertReader: connected to host=" + host + ", port=" + port);
             sslSock.getOutputStream().write("data!".getBytes());//write some data so the connection gets established
-            LOGGER.debug("ServerCertReader: data transfer completed host=" + ldapHost + ", port=" + ldapPort);
+            LOGGER.debug("ServerCertReader: data transfer completed host=" + host + ", port=" + port);
             sslSock.close();
-            LOGGER.debug("ServerCertReader: certificate information read from host=" + ldapHost + ", port=" + ldapPort);
+            LOGGER.debug("ServerCertReader: certificate information read from host=" + host + ", port=" + port);
         } catch (Exception e) {
             final StringBuilder errorMsg = new StringBuilder();
-            errorMsg.append("unable to read ldap server certificates from host=");
-            errorMsg.append(ldapHost).append(", port=").append(ldapPort);
+            errorMsg.append("unable to read server certificates from host=");
+            errorMsg.append(host).append(", port=").append(port);
             errorMsg.append(" error: ");
             errorMsg.append(e.getMessage());
             LOGGER.error("ServerCertReader: " + errorMsg);
@@ -79,7 +98,7 @@ public abstract class X509Utils {
             LOGGER.debug("ServerCertReader: unable to read certificates: null returned from CertReaderTrustManager.getCertificates()");
         } else {
             for (final X509Certificate certificate : certs) {
-                LOGGER.debug("ServerCertReader: read x509 Certificate from host=" + ldapHost + ", port=" + ldapPort + ": \n" + certificate.toString());
+                LOGGER.debug("ServerCertReader: read x509 Certificate from host=" + host + ", port=" + port + ": \n" + certificate.toString());
             }
         }
         LOGGER.debug("ServerCertReader: process completed");
@@ -121,11 +140,40 @@ public abstract class X509Utils {
         }
     }
 
-    public static class PwmTrustManager implements X509TrustManager {
+    public static class PromiscuousTrustManager implements X509TrustManager {
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+
+        public void checkClientTrusted(X509Certificate[] certs, String authType) {
+            logMsg(certs,authType);
+        }
+
+        public void checkServerTrusted(X509Certificate[] certs, String authType) {
+            logMsg(certs,authType);
+        }
+
+        private static void logMsg(X509Certificate[] certs, String authType) {
+            if (certs != null) {
+                for (final X509Certificate cert : certs) {
+                    try {
+                        LOGGER.warn("blind trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString());
+                    } catch (Exception e) {
+                        LOGGER.error("error while decoding certificate: " + e.getMessage());
+                        throw new IllegalStateException(e);
+                    }
+                }
+            }
+        }
+    }
+
+    public static class CertMatchingTrustManager implements X509TrustManager {
         final X509Certificate[] certificates;
+        final boolean validateTimestamps;
 
-        public PwmTrustManager(final X509Certificate[] certificates) {
+        public CertMatchingTrustManager(final Configuration config, final X509Certificate[] certificates) {
             this.certificates = certificates;
+            validateTimestamps = config != null && Boolean.parseBoolean(config.readAppProperty(AppProperty.SECURITY_CERTIFICATES_VALIDATE_TIMESTAMPS));
         }
 
         @Override
@@ -135,15 +183,17 @@ public abstract class X509Utils {
         @Override
         public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
             if (x509Certificates == null) {
-                return;
+                final String errorMsg = "no certificates in configuration trust store for this operation";
+                throw new CertificateException(errorMsg);
             }
 
-
             for (X509Certificate loopCert : x509Certificates) {
                 boolean certTrusted = false;
                 for (X509Certificate storedCert : certificates) {
                     if (loopCert.equals(storedCert)) {
-                        //loopCert.checkValidity();
+                        if (validateTimestamps) {
+                            loopCert.checkValidity();
+                        }
                         certTrusted = true;
                     }
                 }
@@ -151,6 +201,7 @@ public abstract class X509Utils {
                     final String errorMsg = "server certificate {subject=" + loopCert.getSubjectDN().getName() + "} does not match a certificate in the configuration trust store.";
                     throw new CertificateException(errorMsg);
                 }
+                LOGGER.trace("trusting configured certificate: " + makeDebugText(loopCert));
             }
         }
 
@@ -167,11 +218,11 @@ public abstract class X509Utils {
         }
         return result;
     }
-    
+
     public static String makeDetailText(final X509Certificate x509Certificate)
-            throws CertificateEncodingException, PwmUnrecoverableException 
+            throws CertificateEncodingException, PwmUnrecoverableException
     {
-        return x509Certificate.toString() 
+        return x509Certificate.toString()
                 + "\n:MD5 checksum: " + SecureHelper.hash(new ByteArrayInputStream(x509Certificate.getEncoded()),SecureHelper.HashAlgorithm.MD5)
                 + "\n:SHA1 checksum: " + SecureHelper.hash(new ByteArrayInputStream(x509Certificate.getEncoded()),SecureHelper.HashAlgorithm.SHA1)
                 + "\n:SHA2-256 checksum: " + SecureHelper.hash(new ByteArrayInputStream(x509Certificate.getEncoded()),SecureHelper.HashAlgorithm.SHA256)
@@ -179,5 +230,8 @@ public abstract class X509Utils {
 
 
     }
-    
+
+    public static String makeDebugText(final X509Certificate x509Certificate) {
+        return "subject=" + x509Certificate.getSubjectDN().getName() + ", serial=" + x509Certificate.getSerialNumber();
+    }
 }

+ 19 - 4
pwm/servlet/src/password/pwm/util/cache/CacheService.java

@@ -30,6 +30,7 @@ import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.util.JsonUtil;
+import password.pwm.util.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.logging.PwmLogger;
 
@@ -43,7 +44,9 @@ public class CacheService implements PwmService {
     private MemoryCacheStore memoryCacheStore;
     private LocalDBCacheStore localDBCacheStore;
 
-    private STATUS status = STATUS.OPENING;
+    private STATUS status = STATUS.NEW;
+
+    private Date lastTraceOutput;
 
     @Override
     public STATUS status() {
@@ -52,7 +55,8 @@ public class CacheService implements PwmService {
 
     @Override
     public void init(PwmApplication pwmApplication)
-            throws PwmException {
+            throws PwmException
+    {
         final boolean enabled = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_ENABLE));
         if (!enabled) {
             LOGGER.debug("skipping cache service init due to app property setting");
@@ -116,6 +120,7 @@ public class CacheService implements PwmService {
         if (localDBCacheStore != null) {
             localDBCacheStore.store(cacheKey, expirationDate, payload);
         }
+        outputTraceInfo();
     }
 
     public String get(CacheKey cacheKey)
@@ -137,8 +142,19 @@ public class CacheService implements PwmService {
             payload = localDBCacheStore.read(cacheKey);
         }
 
+        outputTraceInfo();
+
+        return payload;
+    }
+
+    private void outputTraceInfo() {
+        if (lastTraceOutput == null || TimeDuration.fromCurrent(lastTraceOutput).isLongerThan(30 * 1000)) {
+            lastTraceOutput = new Date();
+        } else {
+            return;
+        }
+
         final StringBuilder traceOutput = new StringBuilder();
-        traceOutput.append("cache ").append(payload == null ? "MISS" : "HIT");
         if (memoryCacheStore != null) {
             final CacheStoreInfo info = memoryCacheStore.getCacheStoreInfo();
             traceOutput.append(", memCache=");
@@ -150,6 +166,5 @@ public class CacheService implements PwmService {
             traceOutput.append(JsonUtil.serialize(info));
         }
         LOGGER.trace(traceOutput);
-        return payload;
     }
 }

+ 48 - 22
pwm/servlet/src/password/pwm/util/cli/MainClass.java

@@ -119,20 +119,30 @@ public class MainClass {
     )
             throws Exception
     {
+
         final Map<String,Object> options = parseCommandOptions(parameters, args);
         final File applicationPath = figureApplicationPath(MAIN_OPTIONS);
+        out(PwmConstants.SERVLET_VERSION);
+        out("applicationPath=" + applicationPath.getAbsolutePath());
+        PwmApplication.verifyApplicationPath(applicationPath);
         final File configurationFile = locateConfigurationFile(applicationPath);
 
         final ConfigurationReader configReader = loadConfiguration(configurationFile);
         final Configuration config = configReader.getConfiguration();
-        final PwmApplication pwmApplication = parameters.needsPwmApplication
-                ? loadPwmApplication(applicationPath,config,configurationFile,parameters.readOnly)
-                : null;
-        final LocalDB localDB = parameters.needsLocalDB
-                ? pwmApplication == null
-                ? loadPwmDB(config, parameters.readOnly, applicationPath)
-                : pwmApplication.getLocalDB()
-                : null;
+
+        final PwmApplication pwmApplication;
+        final LocalDB localDB;
+        if (parameters.needsPwmApplication) {
+            pwmApplication = loadPwmApplication(applicationPath, config, configurationFile, parameters.readOnly);
+            localDB = pwmApplication.getLocalDB();
+        } else if (parameters.needsLocalDB) {
+            pwmApplication = null;
+            localDB = loadPwmDB(config, parameters.readOnly, applicationPath);
+        } else {
+            pwmApplication = null;
+            localDB = null;
+        }
+
 
         return new CliEnvironment(
                 configReader,
@@ -231,15 +241,38 @@ public class MainClass {
                     final List<String> argList = new LinkedList<>(Arrays.asList(args));
                     argList.remove(0);
 
+                    final CliEnvironment cliEnvironment;
+                    try {
+                        cliEnvironment = createEnv(command.getCliParameters(), argList);
+                    } catch (Exception e) {
+                        System.out.println("unable to establish operating environment: " + e.getMessage());
+                        System.exit(-1);
+                        return;
+                    }
+
                     try {
-                        final CliEnvironment cliEnvironment = createEnv(command.getCliParameters(), argList);
                         command.execute(commandStr, cliEnvironment);
-                    } catch (CliException e) {
+                    } catch (Exception e) {
                         System.out.println(e.getMessage());
-                        System.exit(-1);
+                        //System.exit(-1);
                         return;
                     }
 
+                    if (cliEnvironment.getPwmApplication() != null) {
+                        try {
+                            cliEnvironment.getPwmApplication().shutdown();
+                        } catch (Exception e) {
+                            System.out.println("error closing operating environment: " + e.getMessage());
+                        }
+                    }
+                    if (cliEnvironment.getLocalDB() != null) {
+                        try {
+                            cliEnvironment.getLocalDB().close();
+                        } catch (Exception e) {
+                            System.out.println("error closing LocalDB environment: " + e.getMessage());
+                        }
+                    }
+
                     System.exit(0);
                     return;
                 }
@@ -276,19 +309,12 @@ public class MainClass {
                         }
                     }
                 } else  if (arg.startsWith(OPT_APP_PATH)) {
-                    if (arg.length() < OPT_DEBUG_LEVEL.length() + 2) {
+                    if (arg.length() < OPT_APP_PATH.length() + 2) {
                         out(OPT_APP_PATH + " option must include value (example: -debugLevel=/tmp/applicationPath");
                         System.exit(-1);
                     } else {
-                        final String pathStr = arg.substring(OPT_DEBUG_LEVEL.length() + 1, arg.length());
-                        final File pathValue = new File(pathStr);
-                        if (!pathValue.exists()) {
-                            exitWithError(" specified applicationPath '" + pathStr + "' does not exist");
-                        }
-                        if (!pathValue.isDirectory()) {
-                            exitWithError(" specified applicationPath '" + pathStr + "' must be a directory");
-                        }
-                        MAIN_OPTIONS.applicationPath = pathValue;
+                        final String pathStr = arg.substring(OPT_APP_PATH.length() + 1, arg.length());
+                        MAIN_OPTIONS.applicationPath = new File(pathStr);
                         MAIN_OPTIONS.applicationPathType = PwmApplication.PwmEnvironment.ApplicationPathType.specified;
                     }
                 } else if (arg.equals(OPT_FORCE)) {
@@ -335,7 +361,7 @@ public class MainClass {
     }
 
     static ConfigurationReader loadConfiguration(final File configurationFile) throws Exception {
-        final ConfigurationReader reader = new ConfigurationReader(new File(PwmConstants.DEFAULT_CONFIG_FILE_FILENAME));
+        final ConfigurationReader reader = new ConfigurationReader(configurationFile);
 
         if (reader.getConfigMode() == PwmApplication.MODE.ERROR) {
             final String errorMsg = reader.getConfigFileError() == null ? "error" : reader.getConfigFileError().toDebugStr();

+ 2 - 2
pwm/servlet/src/password/pwm/util/cli/UserReportCommand.java

@@ -95,8 +95,8 @@ public class UserReportCommand extends AbstractCliCommand {
 
 
         CliParameters cliParameters = new CliParameters();
-        cliParameters.commandName = "ExportUserReportCache";
-        cliParameters.description = "Dump a user report to the output file (csv format)";
+        cliParameters.commandName = "ExportUserReportDetail";
+        cliParameters.description = "Output user report details to the output file (csv format)";
         cliParameters.options = Collections.singletonList(outputFileOption);
 
         cliParameters.needsPwmApplication = true;

+ 9 - 1
pwm/servlet/src/password/pwm/util/localdb/Derby_LocalDB.java

@@ -66,8 +66,16 @@ public class Derby_LocalDB extends AbstractJDBC_LocalDB {
     closeConnection(final Connection connection)
             throws SQLException
     {
+        try {
+            DriverManager.getConnection("jdbc:derby:;shutdown=true");
+        } catch (SQLException e) {
+            if ("XJ015".equals(e.getSQLState())) {
+                LOGGER.trace("Derby shutdown succeeded. SQLState=" + e.getSQLState() + ", message=" + e.getMessage());
+            } else {
+                throw e;
+            }
+        }
         connection.close();
-        DriverManager.getConnection("jdbc:derby:;shutdown=true");
     }
 
     @Override

+ 32 - 20
pwm/servlet/src/password/pwm/util/localdb/LocalDB.java

@@ -113,26 +113,38 @@ public interface LocalDB {
         /**
          * Used for various pwm operational data
          */
-        PWM_META,
-        SHAREDHISTORY_META,
-        SHAREDHISTORY_WORDS,
-        WORDLIST_META,
-        WORDLIST_WORDS,
-        SEEDLIST_META,
-        SEEDLIST_WORDS,
-        PWM_STATS,
-        EVENTLOG_EVENTS,
-        EMAIL_QUEUE,
-        SMS_QUEUE,
-        RESPONSE_STORAGE,
-        OTP_SECRET,
-        TOKENS,
-        INTRUDER,
-        AUDIT_EVENTS,
-        USER_CACHE,
-        TEMP,
-        SYSLOG_QUEUE,
-        CACHE,
+        PWM_META(true),
+        SHAREDHISTORY_META(true),
+        SHAREDHISTORY_WORDS(true),
+        WORDLIST_META(true),
+        WORDLIST_WORDS(true),
+        SEEDLIST_META(true),
+        SEEDLIST_WORDS(true),
+        PWM_STATS(true),
+        EVENTLOG_EVENTS(true),
+        EMAIL_QUEUE(true),
+        SMS_QUEUE(true),
+        RESPONSE_STORAGE(true),
+        OTP_SECRET(true),
+        TOKENS(true),
+        INTRUDER(true),
+        AUDIT_EVENTS(true),
+        USER_CACHE(true),
+        TEMP(false),
+        SYSLOG_QUEUE(true),
+        CACHE(false),
+
+        ;
+
+        private final boolean backup;
+
+        DB(final boolean backup) {
+            this.backup = backup;
+        }
+
+        public boolean isBackup() {
+            return backup;
+        }
     }
 
 

+ 2 - 12
pwm/servlet/src/password/pwm/util/localdb/LocalDBUtility.java

@@ -44,20 +44,10 @@ public class LocalDBUtility {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(LocalDBUtility.class);
 
-    final static List<LocalDB.DB> BACKUP_IGNORE_DBs;
     private final LocalDB localDB;
     private int exportLineCounter;
     private int importLineCounter;
 
-    static {
-        final LocalDB.DB[] ignoredDBsArray = {
-                LocalDB.DB.SEEDLIST_META,
-                LocalDB.DB.SEEDLIST_WORDS,
-                LocalDB.DB.WORDLIST_META,
-                LocalDB.DB.WORDLIST_WORDS,
-        };
-        BACKUP_IGNORE_DBs = Collections.unmodifiableList(Arrays.asList(ignoredDBsArray));
-    }
 
     public LocalDBUtility(LocalDB localDB) {
         this.localDB = localDB;
@@ -75,7 +65,7 @@ public class LocalDBUtility {
         if (showLineCount) {
             exportLineCounter = 0;
             for (final LocalDB.DB loopDB : LocalDB.DB.values()) {
-                if (!BACKUP_IGNORE_DBs.contains(loopDB)) {
+                if (loopDB.isBackup()) {
                     exportLineCounter += localDB.size(loopDB);
                 }
             }
@@ -109,7 +99,7 @@ public class LocalDBUtility {
         try {
             csvPrinter.printComment(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " LocalDB export on " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
             for (LocalDB.DB loopDB : LocalDB.DB.values()) {
-                if (!BACKUP_IGNORE_DBs.contains(loopDB)) {
+                if (loopDB.isBackup()) {
                     csvPrinter.printComment("Export of " + loopDB.toString());
                     final LocalDB.LocalDBIterator<String> localDBIterator = localDB.iterator(loopDB);
                     try {

+ 6 - 1
pwm/servlet/src/password/pwm/util/logging/LocalDBLogger.java

@@ -118,7 +118,12 @@ public class LocalDBLogger implements PwmService {
         final PwmLogEvent loopEvent;
         try {
             loopEvent = readEvent(localDBListQueue.getLast());
-            return loopEvent.getDate().getTime();
+            if (loopEvent != null) {
+                final Date tailDate = loopEvent.getDate();
+                if (tailDate != null) {
+                    return tailDate.getTime();
+                }
+            }
         } catch (Exception e) {
             LOGGER.error("unexpected error attempting to determine tail event timestamp: " + e.getMessage());
         }

+ 2 - 2
pwm/servlet/src/password/pwm/util/macro/ExternalRestMacro.java

@@ -26,7 +26,7 @@ import com.google.gson.reflect.TypeToken;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserInfoBean;
-import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmException;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.client.rest.RestClientHelper;
@@ -87,7 +87,7 @@ class ExternalRestMacro extends AbstractMacro {
             } else {
                 return "";
             }
-        } catch (PwmOperationalException e) {
+        } catch (PwmException e) {
             final String errorMsg = "error while executing external macro '" + matchValue + "', error: " + e.getMessage();
             LOGGER.error(errorMsg);
             throw new IllegalStateException(errorMsg);

+ 1 - 1
pwm/servlet/src/password/pwm/util/macro/MacroMachine.java

@@ -242,7 +242,7 @@ public class MacroMachine {
         final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, sessionLabel);
         final UserInfoBean userInfoBean = new UserInfoBean();
         userStatusReader.populateUserInfoBean(userInfoBean, userLocale, userIdentity);
-        return new MacroMachine(pwmApplication, sessionLabel, null, null, userDataReader);
+        return new MacroMachine(pwmApplication, sessionLabel, userInfoBean, null, userDataReader);
     }
 
     public static MacroMachine forNonUserSpecific(

+ 26 - 0
pwm/servlet/src/password/pwm/util/macro/StandardMacros.java

@@ -26,6 +26,7 @@ import com.novell.ldapchai.exception.ChaiException;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
@@ -67,6 +68,7 @@ public abstract class StandardMacros {
         defaultMacros.add(SiteHostMacro.class);
         defaultMacros.add(RandomCharMacro.class);
         defaultMacros.add(UUIDMacro.class);
+        defaultMacros.add(UserLdapProfileMacro.class);
         STANDARD_MACROS = Collections.unmodifiableList(defaultMacros);
     }
 
@@ -336,6 +338,30 @@ public abstract class StandardMacros {
         }
     }
 
+    public static class UserLdapProfileMacro extends AbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@User:LdapProfile@");
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(
+                final String matchValue,
+                final MacroRequestInfo macroRequestInfo
+        ) {
+            final UserInfoBean userInfoBean = macroRequestInfo.getUserInfoBean();
+
+            if (userInfoBean != null) {
+                final UserIdentity userIdentity = userInfoBean.getUserIdentity();
+                if (userIdentity != null) {
+                    return userIdentity.getLdapProfileID();
+                }
+            }
+
+            return "";
+        }
+    }
+
     public static class UserEmailMacro extends AbstractMacro {
         private static final Pattern PATTERN = Pattern.compile("@User:Email@");
 

+ 1 - 1
pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java

@@ -154,7 +154,7 @@ public class ActionExecutor {
             final HttpMethod method = HttpMethod.fromString(actionConfiguration.getMethod().toString());
 
             final PwmHttpClientRequest clientRequest = new PwmHttpClientRequest(method, url, body, headers);
-            final PwmHttpClient client = new PwmHttpClient(pwmApplication, pwmSession);
+            final PwmHttpClient client = new PwmHttpClient(pwmApplication, pwmSession.getLabel());
             final PwmHttpClientResponse clientResponse = client.makeRequest(clientRequest);
 
             if (clientResponse.getStatusCode() != 200) {

+ 5 - 6
pwm/servlet/src/password/pwm/util/operations/otp/LocalDbOtpOperator.java

@@ -103,7 +103,7 @@ public class LocalDbOtpOperator extends AbstractOtpOperator {
     )
             throws PwmUnrecoverableException
     {
-        LOGGER.trace(String.format("Enter: writeOtpUserConfiguration(%s, %s, %s)", theUser, userGUID, otpConfig));
+        LOGGER.trace(pwmSession,String.format("Enter: writeOtpUserConfiguration(%s, %s, %s)", theUser, userGUID, otpConfig));
         if (userGUID == null || userGUID.length() < 1) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_MISSING_GUID, "cannot save otp to localDB, user does not have a pwmGUID"));
         }
@@ -118,12 +118,12 @@ public class LocalDbOtpOperator extends AbstractOtpOperator {
             Configuration config = this.getConfig();
             String value = composeOtpAttribute(otpConfig);
             if (config.readSettingAsBoolean(PwmSetting.OTP_SECRET_ENCRYPT)) {
-                LOGGER.debug("Encrypting OTP secret for storage");
+                LOGGER.debug(pwmSession,"Encrypting OTP secret for storage");
                 value = encryptAttributeValue(value);
             }
 
             localDB.put(LocalDB.DB.OTP_SECRET, userGUID, value);
-            LOGGER.info("saved OTP secret for user in LocalDB");
+            LOGGER.info(pwmSession,"saved OTP secret for user in LocalDB");
         } catch (LocalDBException ex) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_WRITING_OTP_SECRET, "unexpected LocalDB error saving otp to localDB: " + ex.getMessage());
             final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo);
@@ -145,7 +145,7 @@ public class LocalDbOtpOperator extends AbstractOtpOperator {
     )
             throws PwmUnrecoverableException
     {
-        LOGGER.trace(String.format("Enter: clearOtpUserConfiguration(%s, %s)", theUser, userGUID));
+        LOGGER.trace(pwmSession, String.format("Enter: clearOtpUserConfiguration(%s, %s)", theUser, userGUID));
         if (userGUID == null || userGUID.length() < 1) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_MISSING_GUID, "cannot save otp to localDB, user does not have a pwmGUID"));
         }
@@ -158,7 +158,7 @@ public class LocalDbOtpOperator extends AbstractOtpOperator {
 
         try {
             localDB.remove(LocalDB.DB.OTP_SECRET, userGUID);
-            LOGGER.info("cleared OTP secret for user in LocalDB");
+            LOGGER.info(pwmSession, "cleared OTP secret for user in LocalDB");
         } catch (LocalDBException ex) {
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_WRITING_OTP_SECRET, "unexpected error saving otp to localDB: " + ex.getMessage());
             final PwmUnrecoverableException pwmOE = new PwmUnrecoverableException(errorInfo);
@@ -169,7 +169,6 @@ public class LocalDbOtpOperator extends AbstractOtpOperator {
 
     @Override
     public void close() {
-        LOGGER.trace("Enter: close()");
         // No operation
     }
 

+ 3 - 0
pwm/servlet/src/password/pwm/util/queue/AbstractQueueManager.java

@@ -95,6 +95,9 @@ public abstract class AbstractQueueManager implements PwmService {
     }
     
     public Date eldestItem() {
+        if (status != STATUS.OPEN) {
+            return null;
+        }
         final String jsonEvent = sendQueue.peekFirst();
         if (jsonEvent != null) {
             final QueueEvent event = JsonUtil.deserialize(jsonEvent, QueueEvent.class);

+ 1 - 1
pwm/servlet/src/password/pwm/util/queue/SmsQueueManager.java

@@ -124,7 +124,7 @@ public class SmsQueueManager extends AbstractQueueManager {
         }
     }
 
-    protected void shortenMessageIfNeeded(final SmsItemBean smsItem) {
+    protected void shortenMessageIfNeeded(final SmsItemBean smsItem) throws PwmUnrecoverableException {
         final Boolean shorten = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.SMS_USE_URL_SHORTENER);
         if (shorten) {
             final String message = smsItem.getMessage();

+ 7 - 2
pwm/servlet/src/password/pwm/util/stats/Statistic.java

@@ -89,6 +89,7 @@ public enum Statistic {
     HELPDESK_USER_LOOKUP                (Type.INCREMENTOR, "HelpdeskUserLookup", null),
     HELPDESK_TOKENS_SENT                (Type.INCREMENTOR, "HelpdeskTokenSent", null),
     HELPDESK_UNLOCK                     (Type.INCREMENTOR, "HelpdeskUnlock", null),
+    HELPDESK_VERIFY_OTP                 (Type.INCREMENTOR, "HelpdeskVerifyOTP", null),
     REST_STATUS                         (Type.INCREMENTOR, "RestStatus", null),
     REST_CHECKPASSWORD                  (Type.INCREMENTOR, "RestCheckPassword", null),
     REST_SETPASSWORD                    (Type.INCREMENTOR, "RestSetPassword", null),
@@ -153,8 +154,12 @@ public enum Statistic {
     }
 
     public String getLabel(final Locale locale) {
-        final String keyName = "Statistic_Label." + this.getKey();
-        return LocaleHelper.getLocalizedMessage(locale, keyName, null, Admin.class);
+        try {
+            final String keyName = "Statistic_Label." + this.getKey();
+            return LocaleHelper.getLocalizedMessage(locale, keyName, null, Admin.class);
+        } catch (MissingResourceException e) {
+            return "MISSING STATISTIC LABEL for " + this.getKey();
+        }
     }
 
     public String getDescription(final Locale locale) {

+ 2 - 1
pwm/servlet/src/password/pwm/util/stats/StatisticsManager.java

@@ -35,6 +35,7 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.client.PwmHttpClient;
@@ -458,7 +459,7 @@ public class StatisticsManager implements PwmService {
     }
 
     private void publishStatisticsToCloud()
-            throws URISyntaxException, IOException {
+            throws URISyntaxException, IOException, PwmUnrecoverableException {
         final StatsPublishBean statsPublishData;
         {
             final StatisticsBundle bundle = getStatBundleForKey(KEY_CUMULATIVE);

Some files were not shown because too many files changed in this diff