jrivard 10 gadi atpakaļ
vecāks
revīzija
fd8c5c5e39
84 mainītis faili ar 1524 papildinājumiem un 813 dzēšanām
  1. 1 0
      pwm/servlet/src/password/pwm/AppProperty.java
  2. 4 3
      pwm/servlet/src/password/pwm/AppProperty.properties
  3. 2 0
      pwm/servlet/src/password/pwm/PwmConstants.java
  4. 2 2
      pwm/servlet/src/password/pwm/VersionChecker.java
  5. 6 0
      pwm/servlet/src/password/pwm/config/ActionConfiguration.java
  6. 2 14
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  7. 17 91
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  8. 20 11
      pwm/servlet/src/password/pwm/config/PwmSettingCategory.java
  9. 1 0
      pwm/servlet/src/password/pwm/config/PwmSettingSyntax.java
  10. 41 7
      pwm/servlet/src/password/pwm/config/StoredConfiguration.java
  11. 7 14
      pwm/servlet/src/password/pwm/config/option/RecoveryVerificationMethod.java
  12. 16 13
      pwm/servlet/src/password/pwm/config/profile/ForgottenPasswordProfile.java
  13. 116 0
      pwm/servlet/src/password/pwm/config/value/VerificationMethodValue.java
  14. 4 3
      pwm/servlet/src/password/pwm/event/AuditManager.java
  15. 19 0
      pwm/servlet/src/password/pwm/http/HttpMethod.java
  16. 1 1
      pwm/servlet/src/password/pwm/http/SessionManager.java
  17. 1 1
      pwm/servlet/src/password/pwm/http/bean/ChangePasswordBean.java
  18. 164 0
      pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java
  19. 42 0
      pwm/servlet/src/password/pwm/http/client/PwmHttpClientRequest.java
  20. 40 0
      pwm/servlet/src/password/pwm/http/client/PwmHttpClientResponse.java
  21. 5 1
      pwm/servlet/src/password/pwm/http/filter/AuthenticationFilter.java
  22. 8 7
      pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java
  23. 3 2
      pwm/servlet/src/password/pwm/http/servlet/AdminServlet.java
  24. 20 29
      pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java
  25. 2 1
      pwm/servlet/src/password/pwm/http/servlet/ChangePasswordServlet.java
  26. 13 4
      pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java
  27. 1 0
      pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java
  28. 1 4
      pwm/servlet/src/password/pwm/http/servlet/ConfigManagerServlet.java
  29. 8 5
      pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java
  30. 17 12
      pwm/servlet/src/password/pwm/http/servlet/ForgottenUsernameServlet.java
  31. 3 2
      pwm/servlet/src/password/pwm/http/servlet/GuestRegistrationServlet.java
  32. 16 15
      pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java
  33. 1 0
      pwm/servlet/src/password/pwm/http/servlet/LoginServlet.java
  34. 2 1
      pwm/servlet/src/password/pwm/http/servlet/LogoutServlet.java
  35. 22 16
      pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java
  36. 3 2
      pwm/servlet/src/password/pwm/http/servlet/OAuthConsumerServlet.java
  37. 2 1
      pwm/servlet/src/password/pwm/http/servlet/PeopleSearchServlet.java
  38. 1 10
      pwm/servlet/src/password/pwm/http/servlet/PwmServlet.java
  39. 12 10
      pwm/servlet/src/password/pwm/http/servlet/SetupOtpServlet.java
  40. 10 9
      pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java
  41. 3 2
      pwm/servlet/src/password/pwm/http/servlet/ShortcutServlet.java
  42. 8 7
      pwm/servlet/src/password/pwm/http/servlet/UpdateProfileServlet.java
  43. 1 0
      pwm/servlet/src/password/pwm/i18n/Admin.properties
  44. 2 0
      pwm/servlet/src/password/pwm/i18n/Config.properties
  45. 1 0
      pwm/servlet/src/password/pwm/i18n/Display.properties
  46. 4 3
      pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java
  47. 4 1
      pwm/servlet/src/password/pwm/ldap/PasswordChangeProgressChecker.java
  48. 4 2
      pwm/servlet/src/password/pwm/util/AlertHandler.java
  49. 0 33
      pwm/servlet/src/password/pwm/util/Helper.java
  50. 13 8
      pwm/servlet/src/password/pwm/util/JsonUtil.java
  51. 9 0
      pwm/servlet/src/password/pwm/util/PasswordData.java
  52. 2 1
      pwm/servlet/src/password/pwm/util/TinyUrlShortener.java
  53. 32 3
      pwm/servlet/src/password/pwm/util/logging/PwmLogger.java
  54. 5 0
      pwm/servlet/src/password/pwm/util/macro/AbstractMacro.java
  55. 2 0
      pwm/servlet/src/password/pwm/util/macro/MacroImplementation.java
  56. 13 8
      pwm/servlet/src/password/pwm/util/macro/MacroMachine.java
  57. 5 0
      pwm/servlet/src/password/pwm/util/macro/StandardMacros.java
  58. 54 66
      pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java
  59. 6 5
      pwm/servlet/src/password/pwm/util/operations/OtpService.java
  60. 53 25
      pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java
  61. 2 1
      pwm/servlet/src/password/pwm/util/queue/SmsQueueManager.java
  62. 3 6
      pwm/servlet/src/password/pwm/util/stats/StatisticsManager.java
  63. 2 2
      pwm/servlet/src/password/pwm/ws/client/rest/RestClientHelper.java
  64. 7 0
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-attributes.jsp
  65. 13 0
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-enterotp.jsp
  66. 14 2
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-entertoken.jsp
  67. 1 2
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-method.jsp
  68. 13 0
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-responses.jsp
  69. 1 0
      pwm/servlet/web/WEB-INF/jsp/fragment/header-warnings.jsp
  70. 33 38
      pwm/servlet/web/WEB-INF/jsp/helpdesk-detail.jsp
  71. 0 1
      pwm/servlet/web/WEB-INF/jsp/login.jsp
  72. 12 33
      pwm/servlet/web/WEB-INF/jsp/setupotpsecret-existing.jsp
  73. 14 8
      pwm/servlet/web/WEB-INF/jsp/shortcut.jsp
  74. 23 23
      pwm/servlet/web/private/index.jsp
  75. 5 7
      pwm/servlet/web/public/reference/referencedoc.jsp
  76. 49 9
      pwm/servlet/web/public/resources/configStyle.css
  77. 8 9
      pwm/servlet/web/public/resources/js/admin.js
  78. 353 172
      pwm/servlet/web/public/resources/js/configeditor-settings.js
  79. 11 4
      pwm/servlet/web/public/resources/js/configeditor.js
  80. 12 16
      pwm/servlet/web/public/resources/js/configmanager.js
  81. 12 11
      pwm/servlet/web/public/resources/js/main.js
  82. 32 0
      pwm/servlet/web/public/resources/js/otpsecret.js
  83. 10 14
      pwm/servlet/web/public/resources/style.css
  84. 17 0
      pwm/servlet/web/public/resources/themes/water/style.css

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

@@ -33,6 +33,7 @@ public enum AppProperty {
     BACKUP_LOCALDB_COUNT                            ("backup.localdb.count"),
     CACHE_ENABLE                                    ("cache.enable"),
     CACHE_MEMORY_MAX_ITEMS                          ("cache.memory.maxItems"),
+    CACHE_PWRULECHECK_LIFETIME_MS                   ("cache.pwRuleCheckLifetimeMS"),
     CLIENT_ACTIVITY_MAX_EPS_RATE                    ("client.ajax.activityMaxEpsRate"),
     CLIENT_AJAX_PW_WAIT_CHECK_SECONDS               ("client.ajax.changePasswordWaitCheckSeconds"),
     CLIENT_AJAX_TYPING_TIMEOUT                      ("client.ajax.typingTimeout"),

+ 4 - 3
pwm/servlet/src/password/pwm/AppProperty.properties

@@ -27,6 +27,7 @@ backup.config.count=20
 backup.localdb.count=10
 cache.enable=true
 cache.memory.maxItems=100
+cache.pwRuleCheckLifetimeMS=30000
 client.ajax.activityMaxEpsRate=100
 client.ajax.changePasswordWaitCheckSeconds=3
 client.ajax.typingTimeout=20000
@@ -93,9 +94,9 @@ http.session.recycleAtAuth=true
 http.session.validationKeyLength=32
 intruder.retentionTimeMS=86400000
 intruder.cleanupFrequencyMS=3603000
-intruder.minimumDelayPenaltyMS=0
-intruder.maximumDelayPenaltyMS=0
-intruder.delayPerCountMS=700
+intruder.minimumDelayPenaltyMS=300
+intruder.maximumDelayPenaltyMS=3000
+intruder.delayPerCountMS=200
 intruder.delayMaxJitterMS=2000
 ldap.chaiSettings=
 ldap.connection.timeoutMS=30000

+ 2 - 0
pwm/servlet/src/password/pwm/PwmConstants.java

@@ -144,6 +144,8 @@ public abstract class PwmConstants {
         ConfigHasPassword,
 
         CaptchaClientUrl, CaptchaIframeUrl, CaptchaPublicKey,
+
+        ForgottenPasswordOptionalPageView
     }
 
 

+ 2 - 2
pwm/servlet/src/password/pwm/VersionChecker.java

@@ -33,8 +33,8 @@ import password.pwm.error.PwmError;
 import password.pwm.health.HealthRecord;
 import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthTopic;
+import password.pwm.http.client.PwmHttpClient;
 import password.pwm.i18n.Display;
-import password.pwm.util.Helper;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
@@ -184,7 +184,7 @@ public class VersionChecker implements PwmService {
         httpGet.setHeader("Accept", PwmConstants.ContentTypeValue.json.getHeaderValue());
         LOGGER.trace("sending cloud version request to: " + VERSION_CHECK_URL);
 
-        final HttpResponse httpResponse = Helper.getHttpClient(pwmApplication.getConfig()).execute(httpGet);
+        final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpGet);
         if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
             throw new IOException("http response error code: " + httpResponse.getStatusLine().getStatusCode());
         }

+ 6 - 0
pwm/servlet/src/password/pwm/config/ActionConfiguration.java

@@ -33,6 +33,7 @@ public class ActionConfiguration implements Serializable {
     public enum Type { webservice, ldap }
     public enum WebMethod { delete, get, post, put }
     public enum LdapMethod { replace, add, remove }
+    public enum BodyEncoding { none, url }
 
     private String name;
     private String description;
@@ -43,6 +44,7 @@ public class ActionConfiguration implements Serializable {
     private Map<String,String> headers;
     private String url;
     private String body;
+    private BodyEncoding bodyEncoding = BodyEncoding.url;
 
     private LdapMethod ldapMethod = LdapMethod.replace;
     private String attributeName;
@@ -80,6 +82,10 @@ public class ActionConfiguration implements Serializable {
         return body;
     }
 
+    public BodyEncoding getBodyEncoding() {
+        return bodyEncoding;
+    }
+
     public String getAttributeName() {
         return attributeName;
     }

+ 2 - 14
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -644,20 +644,8 @@ public enum PwmSetting {
             "recovery.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.RECOVERY_SETTINGS),
     RECOVERY_PROFILE_QUERY_MATCH(
             "recovery.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_PREVIOUS_AUTH(
-            "recovery.verification.previousAuthentication", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_ATTRIBUTES(
-            "recovery.verification.attributes", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_CHALLENGE_RESPONSE(
-            "recovery.verification.challengeResponse", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_TOKEN(
-            "recovery.verification.token", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_OTP(
-            "recovery.verification.otp", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_REMOTE_RESPONSES(
-            "recovery.verification.remoteResponses", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
-    RECOVERY_VERIFICATION_MIN_OPTIONAL_METHODS(
-            "recovery.verification.minOptionalMethods", PwmSettingSyntax.NUMERIC, PwmSettingCategory.RECOVERY_PROFILE),
+    RECOVERY_VERIFICATION_METHODS(
+            "recovery.verificationMethods", PwmSettingSyntax.VERIFICATION_METHOD, PwmSettingCategory.RECOVERY_PROFILE),
     RECOVERY_TOKEN_SEND_METHOD(
             "challenge.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_PROFILE),
     RECOVERY_ALLOW_UNLOCK(

+ 17 - 91
pwm/servlet/src/password/pwm/config/PwmSetting.xml

@@ -475,6 +475,7 @@
         <label>LDAP URLs</label>
         <description><![CDATA[List of ldap servers in URL format.  Each of these these servers will be used in a fail-over configuration.  The servers are used in order of appearance in this list.  If the first server is unavailable the next available server in the list will be used.  The first server will periodically be checked to see if it has become available.<ul><li>For secure SSL, use the "<i>ldaps://servername:636</i>" format</li><li>For plain-text servers, use "<i>ldap://serverame:389</i>" format (not recommended)</li></ul><p>When using secure connections, the Java virtual machine must trust the directory server, either because you have manually added the public key certificate from the tree to the Java keystore or the certificate is imported into the setting <i>LDAP Server Certificates</i>.<ul><li>Do not use a non-secure connection for anything but the most basic testing purposes (Many LDAP servers will reject password operations on non-secure connections)</li><li>Do not use a load-balancing device for LDAP high availability, instead use the built in LDAP server fail-over functionality</li><li>Do not use a DNS round-robin address</li><li>Avoid using the network address, use the proper fully-qualified domain name address for the server</li></ul>]]></description>
         <regex>^(ldap|ldaps)://[a-zA-Z0-9.-]+:[0-9]+$</regex>
+        <placeholder>ldaps://ldap.example.com:636</placeholder>
         <default>
             <value><![CDATA[ldaps://ldap.example.com:636]]></value>
         </default>
@@ -1642,11 +1643,11 @@
         <label>Disallowed HTTP Inputs</label>
         <description><![CDATA[Disallowed values.  If any input values (on any http parameter) matches these patterns, the matching portion will be stripped from the input.]]></description>
         <default>
-            <value><![CDATA[(?s)(?i)<.*script.*>]]></value>
-            <value><![CDATA[(?s)(?i)<.*xml.*>*]]></value>
-            <value><![CDATA[(?s)(?i)<.*img.*>]]></value>
-            <value><![CDATA[(?s)(?i)<.*src.*>]]></value>
-            <value><![CDATA[(?s)(?i).*href.*]]></value>
+            <value><![CDATA[(?s)(?i)<.*script.*]]></value>
+            <value><![CDATA[(?s)(?i)<.*xml.*]]></value>
+            <value><![CDATA[(?s)(?i)<.*img.*]]></value>
+            <value><![CDATA[(?s)(?i)<.*src.*]]></value>
+            <value><![CDATA[(?s)(?i)<.*href.*]]></value>
         </default>
     </setting>
     <setting key="pwm.requireHTTPS" level="1" required="true">
@@ -2305,6 +2306,13 @@
             <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
         </default>
     </setting>
+    <setting key="recovery.verificationMethods" level="1">
+        <label>Verification Methods</label>
+        <description><![CDATA[Verification Methods]]></description>
+        <default>
+                <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
+        </default>
+    </setting>
     <setting key="recovery.enable" level="1">
         <label>Enable Forgotten Password</label>
         <description><![CDATA[If enabled, forgotten password recovery will be available to users.]]></description>
@@ -2923,7 +2931,6 @@
             <value>sn</value>
             <value>title</value>
             <value>mail</value>
-            <value>telephoneNumber</value>
         </default>
     </setting>
     <setting key="peopleSearch.searchFilter" level="2" required="false">
@@ -3420,87 +3427,6 @@
             <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
         </default>
     </setting>
-    <setting key="recovery.verification.otp" level="1">
-        <label>OTP Verification</label>
-        <description><![CDATA[]]></description>
-        <default>
-            <value><![CDATA[NONE]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-    <setting key="recovery.verification.previousAuthentication" level="1">
-        <label>Previous Authentication Verification</label>
-        <description><![CDATA[User must have previously authenticated (using username and password) using current browser. ]]></description>
-        <default>
-            <value><![CDATA[NONE]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-    <setting key="recovery.verification.attributes" level="1">
-        <label>LDAP Attribute Verification</label>
-        <description><![CDATA[]]></description>
-        <default>
-            <value><![CDATA[NONE]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-    <setting key="recovery.verification.challengeResponse" level="1">
-        <label>Challenge/Response Verification</label>
-        <description><![CDATA[]]></description>
-        <default>
-            <value><![CDATA[REQUIRED]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-    <setting key="recovery.verification.token" level="1">
-        <label>SMS/Email Token Verification</label>
-        <description><![CDATA[]]></description>
-        <default>
-            <value><![CDATA[NONE]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-    <setting key="recovery.verification.remoteResponses" level="1">
-        <label>Remote Web Service Response Verification</label>
-        <description><![CDATA[]]></description>
-        <default>
-            <value><![CDATA[NONE]]></value>
-        </default>
-        <options>
-            <option value="NONE">None</option>
-            <option value="OPTIONAL">Optional</option>
-            <option value="REQUIRED">Required</option>
-        </options>
-    </setting>
-
-    <setting key="recovery.verification.minOptionalMethods" level="1">
-        <label>Minimum Optional Verification Methods Required</label>
-        <description>This setting specifieds that of the Verification methods that are configured as <code>Optional</code>, how many are required to complete the verification process.</description>
-        <default>
-            <value>0</value>
-        </default>
-        <regex>^$|^[0-9]+$</regex>
-    </setting>
     <setting key="helpdesk.token.sendMethod" level="1">
         <label>Token Send Method</label>
         <description><![CDATA[Set the method(s) used for sending the token code to the user.]]></description>
@@ -4238,17 +4164,17 @@
         <description><![CDATA[Policies for forgotten password configuration.]]></description>
     </category>
     <category key="RECOVERY_SETTINGS">
-        <label>Settings</label>
-        <description><![CDATA[Policies for forgotten password configuration.]]></description>
+        <label>Forgotten Password Settings</label>
+        <description><![CDATA[Settings for forgotten password configuration.]]></description>
     </category>
     <category key="RECOVERY_PROFILE" profiles="true" hidden="false">
-        <label>Profiles</label>
+        <label>Forgotten Password Profiles</label>
         <description><![CDATA[Policies for forgotten password configuration.]]></description>
         <profile setting="recovery.profile.list"/>
     </category>
     <category key="ADMINISTRATION">
         <label>Administration</label>
-        <description></description>
+        <description>Administration</description>
     </category>
     <category key="FORGOTTEN_USERNAME">
         <label>Forgotten Username</label>

+ 20 - 11
pwm/servlet/src/password/pwm/config/PwmSettingCategory.java

@@ -34,14 +34,25 @@ public enum PwmSettingCategory {
     PROFILES                    (null),
     MODULES                     (null),
 
-    GENERAL                     (SETTINGS),
-
     LDAP_PROFILE                (LDAP),
     LDAP_GLOBAL                 (LDAP),
     EDIRECTORY                  (LDAP),
     ACTIVE_DIRECTORY            (LDAP),
     ORACLE_DS                   (LDAP),
 
+    GENERAL                     (SETTINGS),
+
+    AUDITING                    (SETTINGS),
+    AUDIT_CONFIG                (AUDITING),
+    USER_HISTORY                (AUDITING),
+    AUDIT_FORWARD               (AUDITING),
+
+    CAPTCHA                     (SETTINGS),
+
+    INTRUDER                    (SETTINGS),
+    INTRUDER_SETTINGS           (INTRUDER),
+    INTRUDER_TIMEOUTS           (INTRUDER),
+
     USER_INTERFACE              (SETTINGS),
     UI_FEATURES                 (USER_INTERFACE),
     UI_WEB                      (USER_INTERFACE),
@@ -58,20 +69,11 @@ public enum PwmSettingCategory {
     APP_SECURITY                (SECURITY),
     WEB_SECURITY                (SECURITY),
 
-    CAPTCHA                     (SETTINGS),
-    INTRUDER                    (SETTINGS),
-    INTRUDER_SETTINGS           (INTRUDER),
-    INTRUDER_TIMEOUTS           (INTRUDER),
 
     TOKEN                       (SETTINGS),
     OTP                         (SETTINGS),
     LOGGING                     (SETTINGS),
 
-    AUDITING                    (SETTINGS),
-    AUDIT_CONFIG                (AUDITING),
-    USER_HISTORY                (AUDITING),
-    AUDIT_FORWARD               (AUDITING),
-
     DATABASE                    (SETTINGS),
     REPORTING                   (SETTINGS),
     
@@ -253,4 +255,11 @@ public enum PwmSettingCategory {
         return sb.toString();
     }
 
+    public static List<PwmSettingCategory> sortedValues(final Locale locale) {
+        final Map<String,PwmSettingCategory> sortedCategories = new TreeMap<String,PwmSettingCategory>();
+        for (final PwmSettingCategory category : PwmSettingCategory.values()) {
+            sortedCategories.put(category.toMenuLocationDebug(null,locale),category);
+        }
+        return Collections.unmodifiableList(new ArrayList<>(sortedCategories.values()));
+    }
 }

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

@@ -45,6 +45,7 @@ public enum PwmSettingSyntax {
     OPTIONLIST(OptionListValue.factory()),
     FILE(FileValue.factory()),
     PROFILE(StringArrayValue.factory()),
+    VERIFICATION_METHOD(VerificationMethodValue.factory()),
 
     ;
 

+ 41 - 7
pwm/servlet/src/password/pwm/config/StoredConfiguration.java

@@ -458,7 +458,7 @@ public class StoredConfiguration implements Serializable {
                     outputObject.put(category.getProfileSetting().getKey(),profiles);
                 }
             }
-            
+
             return linebreaks
                     ? JsonUtil.serialize(outputObject, JsonUtil.Flag.PrettyPrint)
                     : JsonUtil.serialize(outputObject);
@@ -1083,6 +1083,40 @@ public class StoredConfiguration implements Serializable {
                     storedConfiguration.resetSetting(PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID, actor);
                 }
             }
+
+            /*
+            {
+                if (!storedConfiguration.isDefaultValue(PwmSetting.CHALLENGE_REQUIRE_RESPONSES)) {
+                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
+                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
+                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
+                    newMethods.putAll(existingSettings.getMethodSettings());
+                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.disabled);
+                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
+                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
+                            newMethods,
+                            existingSettings.getMinOptionalRequired()
+                    );
+                    storedConfiguration.writeSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default", new VerificationMethodValue(newSettings), actor);
+                }
+            }
+
+            {
+                if (!storedConfiguration.isDefaultValue(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP)) {
+                    final StoredValue configValue = storedConfiguration.readSetting(PwmSetting.RECOVERY_VERIFICATION_METHODS, "default");
+                    final VerificationMethodValue.VerificationMethodSettings existingSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
+                    final Map<RecoveryVerificationMethod,VerificationMethodValue.VerificationMethodSetting> newMethods = new HashMap<>();
+                    newMethods.putAll(existingSettings.getMethodSettings());
+                    VerificationMethodValue.VerificationMethodSetting setting = new VerificationMethodValue.VerificationMethodSetting(VerificationMethodValue.EnabledState.required);
+                    newMethods.put(RecoveryVerificationMethod.CHALLENGE_RESPONSES,setting);
+                    final VerificationMethodValue.VerificationMethodSettings newSettings = new VerificationMethodValue.VerificationMethodSettings(
+                            newMethods,
+                            existingSettings.getMinOptionalRequired()
+                    );
+                    storedConfiguration.writeSetting(PwmSetting.FORGOTTEN_PASSWORD_REQUIRE_OTP, "default", new VerificationMethodValue(newSettings), actor);
+                }
+            }
+            */
         }
     }
 
@@ -1177,7 +1211,7 @@ public class StoredConfiguration implements Serializable {
                         final PwmSetting pwmSetting = (PwmSetting) configRecordID.recordID;
                         final String keyName = pwmSetting.toMenuLocationDebug(configRecordID.getProfileID(), locale);
                         final String debugValue = currentValue.toDebugString(asHtml, locale);
-                        outputMap.put(keyName.toString(),debugValue);
+                        outputMap.put(keyName,debugValue);
                     }
                     break;
 
@@ -1412,20 +1446,20 @@ public class StoredConfiguration implements Serializable {
         }
         return null;
     }
-    
-    public void initNewRandomSecurityKey() 
-            throws PwmUnrecoverableException 
+
+    public void initNewRandomSecurityKey()
+            throws PwmUnrecoverableException
     {
         if (!isDefaultValue(PwmSetting.PWM_SECURITY_KEY)) {
             return;
         }
-        
+
         writeSetting(
                 PwmSetting.PWM_SECURITY_KEY,
                 new PasswordValue(new PasswordData(PwmRandom.getInstance().alphaNumericString(1024))),
                 null
         );
-        
+
         LOGGER.debug("initialized new random security key");
     }
 

+ 7 - 14
pwm/servlet/src/password/pwm/config/option/RecoveryVerificationMethod.java

@@ -23,35 +23,28 @@
 package password.pwm.config.option;
 
 import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Display;
 
 import java.util.Locale;
 
 public enum RecoveryVerificationMethod implements ConfigurationOption {
-    PREVIOUS_AUTH(      false,  PwmSetting.RECOVERY_VERIFICATION_PREVIOUS_AUTH,          Display.Field_VerificationMethodPreviousAuth),
-    ATTRIBUTES(         true,   PwmSetting.RECOVERY_VERIFICATION_ATTRIBUTES,             Display.Field_VerificationMethodAttributes),
-    CHALLENGE_RESPONSES(true,   PwmSetting.RECOVERY_VERIFICATION_CHALLENGE_RESPONSE,     Display.Field_VerificationMethodChallengeResponses),
-    TOKEN(              true,   PwmSetting.RECOVERY_VERIFICATION_TOKEN,                  Display.Field_VerificationMethodToken),
-    OTP(                true,   PwmSetting.RECOVERY_VERIFICATION_OTP,                    Display.Field_VerificationMethodOTP),
-    REMOTE_RESPONSES(   false,  PwmSetting.RECOVERY_VERIFICATION_REMOTE_RESPONSES,       Display.Field_VerificationMethodRemoteResponses),
+    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),
 
     ;
     
     private final boolean userSelectable;
-    private final PwmSetting associatedSetting;
     private final Display displayKey;
 
-    RecoveryVerificationMethod(boolean userSelectable, PwmSetting associatedSetting, Display displayKey) {
+    RecoveryVerificationMethod(boolean userSelectable, Display displayKey) {
         this.userSelectable = userSelectable;
-        this.associatedSetting = associatedSetting;
         this.displayKey = displayKey;
     }
 
-    public PwmSetting getAssociatedSetting() {
-        return associatedSetting;
-    }
-
     public boolean isUserSelectable() {
         return userSelectable;
     }

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

@@ -27,6 +27,7 @@ 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.value.VerificationMethodValue;
 
 import java.util.*;
 
@@ -62,34 +63,36 @@ public class ForgottenPasswordProfile extends AbstractProfile {
     
     public Set<RecoveryVerificationMethod> requiredRecoveryAuthenticationMethods() {
         if (requiredRecoveryVerificationMethods == null) {
-            requiredRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationOptionValue.REQUIRED);
+            requiredRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.required);
         }
         return requiredRecoveryVerificationMethods;
     }
 
     public Set<RecoveryVerificationMethod> optionalRecoveryAuthenticationMethods() {
         if (optionalRecoveryVerificationMethods == null) {
-            optionalRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationOptionValue.OPTIONAL);
+            optionalRecoveryVerificationMethods = readRecoveryAuthMethods(VerificationMethodValue.EnabledState.optional);
         }
         return optionalRecoveryVerificationMethods;
     }
     
-    private Set<RecoveryVerificationMethod> readRecoveryAuthMethods(final VerificationOptionValue matchValue) {
+    private Set<RecoveryVerificationMethod> readRecoveryAuthMethods(final VerificationMethodValue.EnabledState enabledState) {
         final Set<RecoveryVerificationMethod> 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()) {
-            final PwmSetting setting = recoveryVerificationMethod.getAssociatedSetting();
-            final VerificationOptionValue value = this.readSettingAsEnum(setting,VerificationOptionValue.class);
-            if (value != null && value == matchValue) {
-                result.add(recoveryVerificationMethod);
+            if (verificationMethodSettings.getMethodSettings().containsKey(recoveryVerificationMethod)) {
+                if (verificationMethodSettings.getMethodSettings().get(recoveryVerificationMethod).getEnabledState() == enabledState) {
+                    result.add(recoveryVerificationMethod);
+                }
             }
         }
         return result;
     }
-    
-    public static enum VerificationOptionValue {
-        NONE,
-        OPTIONAL,
-        REQUIRED,
-    }
 
+    public int getMinOptionalRequired() {
+        final StoredValue configValue = storedValueMap.get(PwmSetting.RECOVERY_VERIFICATION_METHODS);
+        final VerificationMethodValue.VerificationMethodSettings verificationMethodSettings = (VerificationMethodValue.VerificationMethodSettings)configValue.toNativeObject();
+        return verificationMethodSettings.getMinOptionalRequired();
+    }
 }

+ 116 - 0
pwm/servlet/src/password/pwm/config/value/VerificationMethodValue.java

@@ -0,0 +1,116 @@
+package password.pwm.config.value;
+
+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.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;
+
+public class VerificationMethodValue extends AbstractValue implements StoredValue {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(VerificationMethodValue.class);
+
+    private VerificationMethodSettings value = new VerificationMethodSettings();
+
+
+    public enum EnabledState {
+        disabled,
+        required,
+        optional,
+    }
+
+    public static class VerificationMethodSettings implements Serializable {
+        private Map<RecoveryVerificationMethod,VerificationMethodSetting> methodSettings = new HashMap<>();
+        private int minOptionalRequired = 0;
+
+        public VerificationMethodSettings() {
+        }
+
+        public VerificationMethodSettings(Map<RecoveryVerificationMethod, VerificationMethodSetting> methodSettings, int minOptionalRequired) {
+            this.methodSettings = methodSettings;
+            this.minOptionalRequired = minOptionalRequired;
+        }
+
+        public Map<RecoveryVerificationMethod, VerificationMethodSetting> getMethodSettings() {
+            return Collections.unmodifiableMap(methodSettings);
+        }
+
+        public int getMinOptionalRequired() {
+            return minOptionalRequired;
+        }
+    }
+
+    public static class VerificationMethodSetting {
+        private EnabledState enabledState = EnabledState.disabled;
+
+        public VerificationMethodSetting(EnabledState enabledState) {
+            this.enabledState = enabledState;
+        }
+
+        public EnabledState getEnabledState() {
+            return enabledState;
+        }
+    }
+
+    public VerificationMethodValue() {
+        this(new VerificationMethodSettings());
+    }
+
+    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));
+            }
+        }
+    }
+
+    public static StoredValueFactory factory()
+    {
+        return new StoredValueFactory() {
+            public VerificationMethodValue fromJson(final String input)
+            {
+                if (input == null) {
+                    return new VerificationMethodValue();
+                } else {
+                    VerificationMethodSettings settings = JsonUtil.deserialize(input,VerificationMethodSettings.class);
+                    return new VerificationMethodValue(settings);
+                }
+            }
+
+            public VerificationMethodValue fromXmlElement(Element settingElement, final String key)
+                    throws PwmOperationalException
+            {
+                final Element valueElement = settingElement.getChild("value");
+                final String inputStr = valueElement.getText();
+                VerificationMethodSettings settings = JsonUtil.deserialize(inputStr,VerificationMethodSettings.class);
+                return new VerificationMethodValue(settings);
+            }
+        };
+    }
+
+    @Override
+    public List<Element> toXmlValues(String valueElementName) {
+        final Element valueElement = new Element(valueElementName);
+        valueElement.addContent(new CDATA(JsonUtil.serialize(value)));
+        return Collections.singletonList(valueElement);
+    }
+
+    @Override
+    public Object toNativeObject() {
+        return value;
+    }
+
+    @Override
+    public List<String> validateValue(PwmSetting pwm) {
+        return Collections.emptyList();
+    }
+}

+ 4 - 3
pwm/servlet/src/password/pwm/event/AuditManager.java

@@ -324,13 +324,13 @@ public class AuditManager implements PwmService {
         switch (record.getEventCode().getType()) {
             case SYSTEM:
                 for (final String toAddress : settings.systemEmailAddresses) {
-                    sendAsEmail(pwmApplication, record, toAddress, settings.alertFromAddress);
+                    sendAsEmail(pwmApplication, null, record, toAddress, settings.alertFromAddress);
                 }
                 break;
 
             case USER:
                 for (final String toAddress : settings.userEmailAddresses) {
-                    sendAsEmail(pwmApplication, record, toAddress, settings.alertFromAddress);
+                    sendAsEmail(pwmApplication, null, record, toAddress, settings.alertFromAddress);
                 }
                 break;
         }
@@ -338,6 +338,7 @@ public class AuditManager implements PwmService {
 
     private static void sendAsEmail(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final AuditRecord record,
             final String toAddress,
             final String fromAddress
@@ -360,7 +361,7 @@ public class AuditManager implements PwmService {
         }
 
         final EmailItemBean emailItem = new EmailItemBean(toAddress, fromAddress, subject, body.toString(), null);
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication);
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, sessionLabel);
         pwmApplication.getEmailQueue().submitEmail(emailItem, null, macroMachine);
     }
 

+ 19 - 0
pwm/servlet/src/password/pwm/http/HttpMethod.java

@@ -0,0 +1,19 @@
+package password.pwm.http;
+
+public enum HttpMethod {
+    POST,
+    GET,
+    DELETE,
+    PUT,
+
+    ;
+
+    public static HttpMethod fromString(final String input) {
+        for (final HttpMethod method : HttpMethod.values()) {
+            if (method.toString().equalsIgnoreCase(input)) {
+                return method;
+            }
+        }
+        return null;
+    }
+}

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

@@ -238,7 +238,7 @@ public class SessionManager implements Serializable {
         final UserInfoBean userInfoBean = pwmSession.getSessionStateBean().isAuthenticated()
                 ? pwmSession.getUserInfoBean()
                 : null;
-        return new MacroMachine(pwmApplication, userInfoBean, pwmSession.getLoginInfoBean(), userDataReader);
+        return new MacroMachine(pwmApplication, pwmSession.getLabel(), userInfoBean, pwmSession.getLoginInfoBean(), userDataReader);
     }
 
     public HelpdeskProfile getHelpdeskProfile(final PwmApplication pwmApplication) {

+ 1 - 1
pwm/servlet/src/password/pwm/http/bean/ChangePasswordBean.java

@@ -22,7 +22,7 @@
 
 package password.pwm.http.bean;
 
-import password.pwm.PasswordChangeProgressChecker;
+import password.pwm.ldap.PasswordChangeProgressChecker;
 
 import java.util.Date;
 

+ 164 - 0
pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java

@@ -0,0 +1,164 @@
+package password.pwm.http.client;
+
+import org.apache.http.Header;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.*;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.util.EntityUtils;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+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.logging.PwmLogger;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class PwmHttpClient {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(PwmHttpClient.class);
+
+    private static int classCounter = 0;
+
+    private final PwmApplication pwmApplication;
+    private final PwmSession pwmSession;
+
+    public PwmHttpClient(PwmApplication pwmApplication, PwmSession pwmSession) {
+        this.pwmApplication = pwmApplication;
+        this.pwmSession = pwmSession;
+    }
+
+    public static HttpClient getHttpClient(final Configuration configuration) {
+        final DefaultHttpClient httpClient = new DefaultHttpClient();
+        final String strValue = configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL);
+        if (strValue != null && strValue.length() > 0) {
+            final URI proxyURI = URI.create(strValue);
+
+            final String host = proxyURI.getHost();
+            final int port = proxyURI.getPort();
+            final HttpHost proxy = new HttpHost(host, port);
+            httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
+
+            final String username = proxyURI.getUserInfo();
+            if (username != null && username.length() > 0) {
+                final String password = (username.contains(":")) ? username.split(":")[1] : "";
+                final UsernamePasswordCredentials passwordCredentials = new UsernamePasswordCredentials(username, password);
+                httpClient.getCredentialsProvider().setCredentials(new AuthScope(host, port), passwordCredentials);
+            }
+        }
+        final String userAgent = PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION;
+        httpClient.getParams().setParameter(HttpProtocolParams.USER_AGENT, userAgent);
+        return httpClient;
+    }
+
+    static String entityToDebugString(
+            final String topLine,
+            final Map<String, String> headers,
+            final String body
+    ) {
+        final StringBuilder msg = new StringBuilder();
+        msg.append(topLine);
+        if (body == null || body.isEmpty()) {
+            msg.append(" (no body)");
+        }
+        msg.append("\n");
+        for (final String key : headers.keySet()) {
+            msg.append("  header: ").append(key).append("=").append(headers.get(key)).append("\n");
+        }
+        if (body != null && !body.isEmpty()) {
+            msg.append("  body: ").append(body);
+        }
+
+        return msg.toString();
+    }
+
+    public PwmHttpClientResponse makeRequest(final PwmHttpClientRequest request) throws PwmUnrecoverableException {
+        try {
+            return makeRequestImpl(request);
+        } catch (URISyntaxException | IOException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_SERVICE_UNREACHABLE, "error while making http request: " + e.getMessage()), e);
+        }
+    }
+
+    public PwmHttpClientResponse makeRequestImpl(final PwmHttpClientRequest clientRequest)
+            throws IOException, URISyntaxException {
+        final Date startTime = new Date();
+        final int counter = classCounter++;
+
+        final String requestBody = clientRequest.getBody();
+
+        final HttpRequestBase httpRequest;
+        switch (clientRequest.getMethod()) {
+            case POST:
+                httpRequest = new HttpPost(new URI(clientRequest.getUrl()).toString());
+                if (requestBody != null && !requestBody.isEmpty()) {
+                    ((HttpPost) httpRequest).setEntity(new StringEntity(requestBody, PwmConstants.DEFAULT_CHARSET));
+                }
+                break;
+
+            case PUT:
+                httpRequest = new HttpPut(clientRequest.getUrl());
+                if (clientRequest.getBody() != null && !clientRequest.getBody().isEmpty()) {
+                    ((HttpPut) httpRequest).setEntity(new StringEntity(requestBody, PwmConstants.DEFAULT_CHARSET));
+                }
+                break;
+
+            case GET:
+                httpRequest = new HttpGet(clientRequest.getUrl());
+                break;
+
+            case DELETE:
+                httpRequest = new HttpDelete(clientRequest.getUrl());
+                break;
+
+            default:
+                throw new IllegalStateException("http method not yet implemented");
+        }
+
+        if (clientRequest.getHeaders() != null) {
+            for (final String key : clientRequest.getHeaders().keySet()) {
+                final String value = clientRequest.getHeaders().get(key);
+                httpRequest.addHeader(key, value);
+            }
+        }
+
+        final HttpClient httpClient = getHttpClient(pwmApplication.getConfig());
+        LOGGER.trace(pwmSession, "preparing to send (id=" + counter + ") " + clientRequest.toDebugString());
+
+        final HttpResponse httpResponse = httpClient.execute(httpRequest);
+        final String responseBody = EntityUtils.toString(httpResponse.getEntity());
+        final Map<String, String> responseHeaders = new LinkedHashMap<>();
+        if (httpResponse.getAllHeaders() != null) {
+            for (final Header header : httpResponse.getAllHeaders()) {
+                responseHeaders.put(header.getName(), header.getValue());
+            }
+        }
+
+        final PwmHttpClientResponse httpClientResponse = new PwmHttpClientResponse(
+                httpResponse.getStatusLine().getStatusCode(),
+                httpResponse.getStatusLine().getReasonPhrase(),
+                responseHeaders,
+                responseBody
+        );
+
+        final TimeDuration duration = TimeDuration.fromCurrent(startTime);
+        LOGGER.trace(pwmSession, "received response (id=" + counter + ") in " + duration.asCompactString() + ": " + httpClientResponse.toDebugString());
+        return httpClientResponse;
+    }
+}
+

+ 42 - 0
pwm/servlet/src/password/pwm/http/client/PwmHttpClientRequest.java

@@ -0,0 +1,42 @@
+package password.pwm.http.client;
+
+import password.pwm.http.HttpMethod;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+
+public class PwmHttpClientRequest implements Serializable {
+    private final HttpMethod method;
+    private final String url;
+    private final String body;
+    private 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);
+    }
+
+    public HttpMethod getMethod() {
+        return method;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public String toDebugString() {
+        return PwmHttpClient.entityToDebugString("HTTP " + method + " request to " + url, headers, body);
+    }
+
+}

+ 40 - 0
pwm/servlet/src/password/pwm/http/client/PwmHttpClientResponse.java

@@ -0,0 +1,40 @@
+package password.pwm.http.client;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Map;
+
+public class PwmHttpClientResponse implements Serializable {
+    private final int statusCode;
+    private final String statusPhrase;
+    private final Map<String,String> headers;
+    private final String body;
+
+    public PwmHttpClientResponse(int statusCode, String statusPhrase, Map<String, String> headers, String body) {
+        this.statusCode = statusCode;
+        this.statusPhrase = statusPhrase;
+        this.headers = headers == null ? Collections.<String,String>emptyMap() : Collections.unmodifiableMap(headers);;
+        this.body = body;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public String getStatusPhrase() {
+        return statusPhrase;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public String toDebugString() {
+        return PwmHttpClient.entityToDebugString("HTTP response status " + statusCode + " " + statusPhrase, headers, body);
+    }
+
+}

+ 5 - 1
pwm/servlet/src/password/pwm/http/filter/AuthenticationFilter.java

@@ -23,7 +23,10 @@
 package password.pwm.http.filter;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.*;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.Validator;
 import password.pwm.bean.SessionStateBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
@@ -36,6 +39,7 @@ import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.OAuthConsumerServlet;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.LocaleHelper;
+import password.pwm.ldap.PasswordChangeProgressChecker;
 import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;

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

@@ -35,6 +35,7 @@ import password.pwm.config.option.MessageSendMethod;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.AuditRecord;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ActivateUserBean;
@@ -71,21 +72,21 @@ public class ActivateUserServlet extends PwmServlet {
     private static final String TOKEN_NAME = ActivateUserServlet.class.getName();
 
     public enum ActivateUserAction implements PwmServlet.ProcessAction {
-        activate(PwmServlet.HttpMethod.POST),
-        enterCode(PwmServlet.HttpMethod.POST, HttpMethod.GET),
-        reset(PwmServlet.HttpMethod.POST),
-        agree(PwmServlet.HttpMethod.POST),
+        activate(HttpMethod.POST),
+        enterCode(HttpMethod.POST, HttpMethod.GET),
+        reset(HttpMethod.POST),
+        agree(HttpMethod.POST),
 
         ;
 
-        private final Collection<PwmServlet.HttpMethod> method;
+        private final Collection<HttpMethod> method;
 
-        ActivateUserAction(PwmServlet.HttpMethod... method)
+        ActivateUserAction(HttpMethod... method)
         {
             this.method = Collections.unmodifiableList(Arrays.asList(method));
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return method;
         }

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

@@ -31,6 +31,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.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.AdminBean;
 import password.pwm.util.Helper;
@@ -61,12 +62,12 @@ public class AdminServlet extends PwmServlet {
 
         private final Collection<HttpMethod> method;
 
-        AdminAction(PwmServlet.HttpMethod... method)
+        AdminAction(HttpMethod... method)
         {
             this.method = Collections.unmodifiableList(Arrays.asList(method));
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return method;
         }

+ 20 - 29
pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java

@@ -23,12 +23,6 @@
 package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.util.EntityUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -37,9 +31,12 @@ 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.PwmSession;
-import password.pwm.util.Helper;
+import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.http.client.PwmHttpClientResponse;
 import password.pwm.util.PasswordData;
 import password.pwm.util.ServletHelper;
 import password.pwm.util.TimeDuration;
@@ -49,7 +46,6 @@ import password.pwm.util.stats.StatisticsManager;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
-import java.net.URI;
 import java.util.Collection;
 import java.util.Collections;
 
@@ -65,9 +61,9 @@ public class CaptchaServlet extends PwmServlet {
         doVerify,
         ;
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
-            return Collections.singletonList(PwmServlet.HttpMethod.POST);
+            return Collections.singletonList(HttpMethod.POST);
         }
     }
 
@@ -167,24 +163,24 @@ public class CaptchaServlet extends PwmServlet {
         bodyText.append("response=").append(pwmRequest.readParameterAsString("recaptcha_response_field"));
 
         try {
-            final String configuredURI = pwmApplication.getConfig().readAppProperty(AppProperty.RECAPTCHA_VALIDATE_URL);
-            final URI requestURI = new URI(configuredURI);
-            final HttpPost httpPost = new HttpPost(requestURI.toString());
-            httpPost.setHeader("Content-Type", PwmConstants.ContentTypeValue.form.getHeaderValue());
-            httpPost.setEntity(new StringEntity(bodyText.toString(),PwmConstants.DEFAULT_CHARSET));
-            LOGGER.debug(pwmRequest, "sending reCaptcha verification request: " + httpRequestToDebugString(httpPost));
-
-            final HttpResponse httpResponse = Helper.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
-            if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+            final PwmHttpClientRequest clientRequest = new PwmHttpClientRequest(
+                    HttpMethod.POST,
+                    pwmApplication.getConfig().readAppProperty(AppProperty.RECAPTCHA_VALIDATE_URL),
+                    bodyText.toString(),
+                    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 PwmHttpClientResponse clientResponse = client.makeRequest(clientRequest);
+
+            if (clientResponse.getStatusCode() != 200) {
                 throw new PwmUnrecoverableException(new ErrorInformation(
                         PwmError.ERROR_CAPTCHA_API_ERROR,
-                        "unexpected HTTP status code (" + httpResponse.getStatusLine().getStatusCode() + ")"
+                        "unexpected HTTP status code (" + clientResponse.getStatusCode() + ")"
                 ));
             }
 
-            final String responseBody = EntityUtils.toString(httpResponse.getEntity());
-
-            final String[] splitResponse = responseBody.split("\n");
+            final String[] splitResponse = clientResponse.getBody().split("\n");
             if (splitResponse.length > 0 && Boolean.parseBoolean(splitResponse[0])) {
                 return true;
             }
@@ -194,7 +190,7 @@ public class CaptchaServlet extends PwmServlet {
                 LOGGER.debug(pwmRequest, "reCaptcha error response: " + errorCode);
             }
         } catch (Exception e) {
-            final String errorMsg = "unexpected error during recpatcha API execution: " + e.getMessage();
+            final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage();
             LOGGER.error(errorMsg,e);
             final ErrorInformation errorInfo = new ErrorInformation(PwmError.ERROR_CAPTCHA_API_ERROR, errorMsg);
             final PwmUnrecoverableException pwmE = new PwmUnrecoverableException(errorInfo);
@@ -205,11 +201,6 @@ public class CaptchaServlet extends PwmServlet {
         return false;
     }
 
-    private static String httpRequestToDebugString(final HttpRequest httpRequest) {
-        return httpRequest.getRequestLine().toString();
-    }
-
-
     private void forwardToOriginalLocation(
             final PwmRequest pwmRequest
     )

+ 2 - 1
pwm/servlet/src/password/pwm/http/servlet/ChangePasswordServlet.java

@@ -27,7 +27,6 @@ import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.impl.oracleds.entry.OracleDSEntries;
 import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.PasswordChangeProgressChecker;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -44,11 +43,13 @@ import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.AuditRecord;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ChangePasswordBean;
 import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.i18n.Message;
+import password.pwm.ldap.PasswordChangeProgressChecker;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.PasswordData;

+ 13 - 4
pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java

@@ -29,11 +29,13 @@ 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;
 import password.pwm.error.*;
 import password.pwm.health.*;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigManagerBean;
@@ -707,7 +709,7 @@ public class ConfigEditorServlet extends PwmServlet {
             navigationData.add(categoryInfo);
         }
 
-        {
+        { // home menu item
             final Map<String, Object> categoryInfo = new HashMap<>();
             categoryInfo.put("id", "HOME");
             categoryInfo.put("name", LocaleHelper.getLocalizedMessage(pwmRequest.getLocale(),Config.MenuItem_Home,pwmRequest.getConfig()));
@@ -716,7 +718,7 @@ public class ConfigEditorServlet extends PwmServlet {
         }
 
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
-        for (final PwmSettingCategory loopCategory : PwmSettingCategory.values()) {
+        for (final PwmSettingCategory loopCategory : PwmSettingCategory.sortedValues(pwmRequest.getLocale())) {
             if (NavTreeHelper.categoryMatcher(loopCategory, storedConfiguration, modifiedSettingsOnly, level)) {
                 final Map<String, Object> categoryInfo = new LinkedHashMap<>();
                 categoryInfo.put("id", loopCategory.getKey());
@@ -797,7 +799,7 @@ public class ConfigEditorServlet extends PwmServlet {
             navigationData.add(categoryInfo);
         }
 
-        LOGGER.debug(pwmRequest,"completed navigation tree data request in " + TimeDuration.fromCurrent(startTime));
+        LOGGER.trace(pwmRequest,"completed navigation tree data request in " + TimeDuration.fromCurrent(startTime).asCompactString());
         pwmRequest.outputJsonResult(new RestResultBean(navigationData));
     }
 
@@ -948,6 +950,13 @@ public class ConfigEditorServlet extends PwmServlet {
             }
             returnMap.put("templates", templateMap);
         }
+        {
+            final LinkedHashMap<String, Object> verificationMethodMap = new LinkedHashMap<>();
+            for (final RecoveryVerificationMethod recoveryVerificationMethod : RecoveryVerificationMethod.values()) {
+                verificationMethodMap.put(recoveryVerificationMethod.toString(), recoveryVerificationMethod.getLabel(pwmRequest.getConfig(), pwmRequest.getLocale()));
+            }
+            returnMap.put("verificationMethods",verificationMethodMap);
+        }
 
         final RestResultBean restResultBean = new RestResultBean();
 
@@ -967,7 +976,7 @@ public class ConfigEditorServlet extends PwmServlet {
             if (pwmRequest.isAuthenticated()) {
                 macroMachine = pwmRequest.getPwmSession().getSessionManager().getMacroMachine(pwmRequest.getPwmApplication());
             } else {
-                macroMachine = MacroMachine.forNonUserSpecific(pwmRequest.getPwmApplication());
+                macroMachine = MacroMachine.forNonUserSpecific(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
             }
             final String input = inputMap.get("input");
             final String output = macroMachine.expandMacros(input);

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

@@ -38,6 +38,7 @@ import password.pwm.config.value.*;
 import password.pwm.error.*;
 import password.pwm.health.*;
 import password.pwm.http.ContextManager;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigGuideBean;

+ 1 - 4
pwm/servlet/src/password/pwm/http/servlet/ConfigManagerServlet.java

@@ -34,10 +34,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
-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.http.bean.ConfigManagerBean;
 import password.pwm.i18n.Config;
 import password.pwm.i18n.Display;

+ 8 - 5
pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java

@@ -44,6 +44,7 @@ import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ForgottenPasswordBean;
@@ -105,14 +106,14 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         ;
 
-        private final Collection<PwmServlet.HttpMethod> method;
+        private final Collection<HttpMethod> method;
 
-        ForgottenPasswordAction(PwmServlet.HttpMethod... method)
+        ForgottenPasswordAction(HttpMethod... method)
         {
             this.method = Collections.unmodifiableList(Arrays.asList(method));
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return method;
         }
@@ -281,6 +282,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         if (remainingAvailableOptionalMethods.contains(requestedChoice)) {
             forgottenPasswordBean.getProgress().setInProgressVerificationMethod(requestedChoice);
+            pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView,"true");
             forwardUserBasedOnRecoveryMethod(pwmRequest, requestedChoice);
             return;
         } else if (requestedChoice != null) {
@@ -587,6 +589,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
             if (progress.getSatisfiedMethods().contains(progress.getInProgressVerificationMethod())) {
                 progress.setInProgressVerificationMethod(null);
             } else {
+                pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView,"true");
                 forwardUserBasedOnRecoveryMethod(pwmRequest, progress.getInProgressVerificationMethod());
                 return;
             }
@@ -1161,13 +1164,13 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         final Set<RecoveryVerificationMethod> requiredRecoveryVerificationMethods = forgottenPasswordProfile.requiredRecoveryAuthenticationMethods();
         final Set<RecoveryVerificationMethod> optionalRecoveryVerificationMethods = forgottenPasswordProfile.optionalRecoveryAuthenticationMethods();
-        final int minimumOptionalRecoveryAuthMethds = (int)forgottenPasswordProfile.readSettingAsLong(PwmSetting.RECOVERY_VERIFICATION_MIN_OPTIONAL_METHODS);
+        final int minimumOptionalRecoveryAuthMethods = forgottenPasswordProfile.getMinOptionalRequired();
         final boolean allowWhenLdapIntruderLocked = forgottenPasswordProfile.readSettingAsBoolean(PwmSetting.RECOVERY_ALLOW_WHEN_LOCKED);
 
         return new ForgottenPasswordBean.RecoveryFlags(
                 requiredRecoveryVerificationMethods,
                 optionalRecoveryVerificationMethods,
-                minimumOptionalRecoveryAuthMethds,
+                minimumOptionalRecoveryAuthMethods,
                 allowWhenLdapIntruderLocked,
                 tokenSendMethod
         );

+ 17 - 12
pwm/servlet/src/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -34,6 +34,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.i18n.Message;
@@ -56,9 +57,9 @@ public class ForgottenUsernameServlet extends PwmServlet {
         search,
         ;
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
-            return Collections.singletonList(PwmServlet.HttpMethod.POST);
+            return Collections.singletonList(HttpMethod.POST);
         }
     }
 
@@ -212,6 +213,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
 
         sendMessageViaMethod(
                 pwmApplication,
+                pwmSession.getLabel(),
                 forgottenUserInfo,
                 messageSendMethod,
                 emailItemBean,
@@ -222,6 +224,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
 
     private static void sendMessageViaMethod(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final UserInfoBean userInfoBean,
             final MessageSendMethod messageSendMethod,
             final EmailItemBean emailItemBean,
@@ -244,8 +247,8 @@ public class ForgottenUsernameServlet extends PwmServlet {
 
             case BOTH:
                 // Send both email and SMS, success if one of both succeeds
-                final ErrorInformation err1 = sendEmailViaMethod(pwmApplication, userInfoBean, emailItemBean);
-                final ErrorInformation err2 = sendSmsViaMethod(pwmApplication, userInfoBean, smsMessage);
+                final ErrorInformation err1 = sendEmailViaMethod(pwmApplication, sessionLabel, userInfoBean, emailItemBean);
+                final ErrorInformation err2 = sendSmsViaMethod(pwmApplication, sessionLabel, userInfoBean, smsMessage);
                 if (err1 != null) {
                     error = err1;
                 } else if (err2 != null) {
@@ -254,26 +257,26 @@ public class ForgottenUsernameServlet extends PwmServlet {
                 break;
             case EMAILFIRST:
                 // Send email first, try SMS if email is not available
-                error = sendEmailViaMethod(pwmApplication, userInfoBean, emailItemBean);
+                error = sendEmailViaMethod(pwmApplication, sessionLabel, userInfoBean, emailItemBean);
                 if (error != null) {
-                    error = sendSmsViaMethod(pwmApplication, userInfoBean, smsMessage);
+                    error = sendSmsViaMethod(pwmApplication, sessionLabel, userInfoBean, smsMessage);
                 }
                 break;
             case SMSFIRST:
                 // Send SMS first, try email if SMS is not available
-                error = sendSmsViaMethod(pwmApplication, userInfoBean, smsMessage);
+                error = sendSmsViaMethod(pwmApplication, sessionLabel, userInfoBean, smsMessage);
                 if (error != null) {
-                    error = sendEmailViaMethod(pwmApplication, userInfoBean, emailItemBean);
+                    error = sendEmailViaMethod(pwmApplication, sessionLabel, userInfoBean, emailItemBean);
                 }
                 break;
             case SMSONLY:
                 // Only try SMS
-                error = sendSmsViaMethod(pwmApplication, userInfoBean, smsMessage);
+                error = sendSmsViaMethod(pwmApplication, sessionLabel, userInfoBean, smsMessage);
                 break;
             case EMAILONLY:
             default:
                 // Only try email
-                error = sendEmailViaMethod(pwmApplication, userInfoBean, emailItemBean);
+                error = sendEmailViaMethod(pwmApplication, sessionLabel, userInfoBean, emailItemBean);
                 break;
         }
         if (error != null) {
@@ -283,6 +286,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
 
     private static ErrorInformation sendSmsViaMethod(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final UserInfoBean userInfoBean,
             final String smsMessage
     )
@@ -295,7 +299,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
         }
 
         final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmApplication, userInfoBean.getUserIdentity());
-        final MacroMachine macroMachine = new MacroMachine(pwmApplication, userInfoBean, null, userDataReader);
+        final MacroMachine macroMachine = new MacroMachine(pwmApplication, sessionLabel, userInfoBean, null, userDataReader);
 
         final SmsItemBean smsItem = new SmsItemBean(toNumber, smsMessage);
         pwmApplication.sendSmsUsingQueue(smsItem, macroMachine);
@@ -304,6 +308,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
 
     private static ErrorInformation sendEmailViaMethod(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final UserInfoBean userInfoBean,
             final EmailItemBean emailItemBean
     )
@@ -315,7 +320,7 @@ public class ForgottenUsernameServlet extends PwmServlet {
         }
 
         final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmApplication, userInfoBean.getUserIdentity());
-        final MacroMachine macroMachine = new MacroMachine(pwmApplication, userInfoBean, null, userDataReader);
+        final MacroMachine macroMachine = new MacroMachine(pwmApplication, sessionLabel, userInfoBean, null, userDataReader);
 
         pwmApplication.getEmailQueue().submitEmail(emailItemBean, userInfoBean, macroMachine);
 

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

@@ -40,6 +40,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.GuestRegistrationBean;
@@ -87,9 +88,9 @@ public class GuestRegistrationServlet extends PwmServlet {
         selectPage,
         ;
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
-            return Collections.singletonList(PwmServlet.HttpMethod.POST);
+            return Collections.singletonList(HttpMethod.POST);
         }
     }
 

+ 16 - 15
pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java

@@ -44,6 +44,7 @@ import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.HelpdeskAuditRecord;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.HelpdeskBean;
@@ -85,25 +86,25 @@ public class HelpdeskServlet extends PwmServlet {
     private static final String TOKEN_NAME = HelpdeskServlet.class.getName();
 
     public enum HelpdeskAction implements PwmServlet.ProcessAction {
-        doUnlock(PwmServlet.HttpMethod.POST),
-        doClearOtpSecret(PwmServlet.HttpMethod.POST),
-        search(PwmServlet.HttpMethod.POST),
-        detail(PwmServlet.HttpMethod.POST),
-        executeAction(PwmServlet.HttpMethod.POST),
-        deleteUser(PwmServlet.HttpMethod.POST),
-        validateOtpCode(PwmServlet.HttpMethod.POST),
-        sendVerificationToken(PwmServlet.HttpMethod.POST),
+        doUnlock(HttpMethod.POST),
+        doClearOtpSecret(HttpMethod.POST),
+        search(HttpMethod.POST),
+        detail(HttpMethod.POST),
+        executeAction(HttpMethod.POST),
+        deleteUser(HttpMethod.POST),
+        validateOtpCode(HttpMethod.POST),
+        sendVerificationToken(HttpMethod.POST),
 
         ;
 
-        private final PwmServlet.HttpMethod method;
+        private final HttpMethod method;
 
-        HelpdeskAction(PwmServlet.HttpMethod method)
+        HelpdeskAction(HttpMethod method)
         {
             this.method = method;
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return Collections.singletonList(method);
         }
@@ -515,7 +516,7 @@ public class HelpdeskServlet extends PwmServlet {
 
         final String configuredDisplayName = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_DETAIL_DISPLAY_NAME);
         if (configuredDisplayName != null && !configuredDisplayName.isEmpty()) {
-            final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), helpdeskBean.getUserInfoBean(), null, userDataReader);
+            final MacroMachine macroMachine = new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), helpdeskBean.getUserInfoBean(), null, userDataReader);
             final String displayName = macroMachine.expandMacros(configuredDisplayName);
             helpdeskBean.setUserDisplayName(displayName);
         }
@@ -674,7 +675,7 @@ public class HelpdeskServlet extends PwmServlet {
 
         final UserInfoBean userInfoBean = helpdeskBean.getUserInfoBean();
         final UserIdentity userIdentity = userInfoBean.getUserIdentity();
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmRequest.getPwmApplication());
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
         final String configuredTokenString = config.readAppProperty(AppProperty.HELPDESK_TOKEN_VALUE);
         final String tokenKey = macroMachine.expandMacros(configuredTokenString);
 
@@ -741,8 +742,8 @@ public class HelpdeskServlet extends PwmServlet {
             return;
         }
 
-        if (!helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON)) {
-            final String errorMsg = "clear responses request, but helpdesk clear responses button is not enabled";
+        if (!helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_OTP_BUTTON)) {
+            final String errorMsg = "clear responses request, but helpdesk clear otp button is not enabled";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, errorMsg);
             LOGGER.error(pwmRequest, errorMsg);
             pwmRequest.setResponseError(errorInformation);

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

@@ -30,6 +30,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;

+ 2 - 1
pwm/servlet/src/password/pwm/http/servlet/LogoutServlet.java

@@ -28,6 +28,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.util.StringUtil;
@@ -47,7 +48,7 @@ public class LogoutServlet extends PwmServlet {
 
         public Collection<HttpMethod> permittedMethods()
         {
-            return Collections.singletonList(PwmServlet.HttpMethod.GET);
+            return Collections.singletonList(HttpMethod.GET);
         }
     }
 

+ 22 - 16
pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java

@@ -34,6 +34,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserInfoBean;
 import password.pwm.config.*;
@@ -42,6 +43,7 @@ import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.LoginInfoBean;
@@ -85,25 +87,25 @@ public class NewUserServlet extends PwmServlet {
     }
 
     public enum NewUserAction implements PwmServlet.ProcessAction {
-        profileChoice(PwmServlet.HttpMethod.POST),
-        checkProgress(PwmServlet.HttpMethod.GET),
-        complete(PwmServlet.HttpMethod.GET),
-        processForm(PwmServlet.HttpMethod.POST),
-        validate(PwmServlet.HttpMethod.POST),
-        enterCode(PwmServlet.HttpMethod.POST, HttpMethod.GET),
-        reset(PwmServlet.HttpMethod.POST),
-        agree(PwmServlet.HttpMethod.POST),
+        profileChoice(HttpMethod.POST),
+        checkProgress(HttpMethod.GET),
+        complete(HttpMethod.GET),
+        processForm(HttpMethod.POST),
+        validate(HttpMethod.POST),
+        enterCode(HttpMethod.POST, HttpMethod.GET),
+        reset(HttpMethod.POST),
+        agree(HttpMethod.POST),
 
         ;
 
-        private final Collection<PwmServlet.HttpMethod> method;
+        private final Collection<HttpMethod> method;
 
-        NewUserAction(PwmServlet.HttpMethod... method)
+        NewUserAction(HttpMethod... method)
         {
             this.method = Collections.unmodifiableList(Arrays.asList(method));
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return method;
         }
@@ -258,8 +260,11 @@ public class NewUserServlet extends PwmServlet {
                 pwmSession.getSessionStateBean().getLocale());
         if (newUserAgreementText != null && !newUserAgreementText.isEmpty()) {
             if (!newUserBean.isAgreementPassed()) {
-                final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication,
-                        newUserBean.getNewUserForm());
+                final MacroMachine macroMachine = createMacroMachineForNewUser(
+                        pwmApplication,
+                        pwmRequest.getSessionLabel(),
+                        newUserBean.getNewUserForm()
+                );
                 final String expandedText = macroMachine.expandMacros(newUserAgreementText);
                 pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.AgreementText, expandedText);
                 pwmRequest.forwardToJsp(PwmConstants.JSP_URL.NEW_USER_AGREEMENT);
@@ -668,7 +673,7 @@ public class NewUserServlet extends PwmServlet {
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
-        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmRequest.getPwmApplication(), formValues);
+        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), formValues);
         final NewUserProfile newUserProfile = getNewUserProfile(pwmRequest);
         final List<String> configuredNames = newUserProfile.readSettingAsStringArray(PwmSetting.NEWUSER_USERNAME_DEFINITION);
         final List<String> failedValues = new ArrayList<>();
@@ -788,7 +793,7 @@ public class NewUserServlet extends PwmServlet {
         final NewUserBean newUserBean = pwmRequest.getPwmSession().getNewUserBean();
         final Configuration config = pwmApplication.getConfig();
         final Map<String, String> tokenPayloadMap = NewUserFormUtils.toTokenPayload(pwmRequest, newUserBean.getNewUserForm());
-        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, newUserBean.getNewUserForm());
+        final MacroMachine macroMachine = createMacroMachineForNewUser(pwmApplication, pwmRequest.getSessionLabel(), newUserBean.getNewUserForm());
 
         switch (phase) {
             case SMS: {
@@ -957,6 +962,7 @@ public class NewUserServlet extends PwmServlet {
 
     private static MacroMachine createMacroMachineForNewUser(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final NewUserBean.NewUserForm newUserForm
     )
             throws PwmUnrecoverableException
@@ -975,7 +981,7 @@ public class NewUserServlet extends PwmServlet {
         stubLoginBean.setUserCurrentPassword(newUserForm.getNewUserPassword());
 
         final UserDataReader stubReader = new NewUserUserDataReader(formValues);
-        return new MacroMachine(pwmApplication, stubUserBean, stubLoginBean, stubReader);
+        return new MacroMachine(pwmApplication, sessionLabel, stubUserBean, stubLoginBean, stubReader);
     }
 
 

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

@@ -41,6 +41,7 @@ import password.pwm.error.PwmUnrecoverableException;
 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.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.util.*;
@@ -264,7 +265,7 @@ public class OAuthConsumerServlet extends PwmServlet {
         } catch (PwmUnrecoverableException | IOException e) {
             LOGGER.error(pwmRequest, "error while processing oauth token refresh:" + e.getMessage());
         }
-        LOGGER.error(pwmRequest, "unable to refresh oauth token for user, unauthenticating session");
+        LOGGER.error(pwmRequest, "unable to refresh oauth token for user, unauthenticated session");
         pwmRequest.getPwmSession().unauthenticateUser();
         return true;
     }
@@ -356,7 +357,7 @@ public class OAuthConsumerServlet extends PwmServlet {
         bodyEntity.setContentType(PwmConstants.ContentTypeValue.form.getHeaderValue());
         httpPost.setEntity(bodyEntity);
 
-        final HttpResponse httpResponse = Helper.getHttpClient(pwmRequest.getConfig()).execute(httpPost);
+        final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmRequest.getConfig()).execute(httpPost);
         final String bodyResponse = EntityUtils.toString(httpResponse.getEntity());
 
         final StringBuilder debugOutput = new StringBuilder();

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

@@ -38,6 +38,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.*;
@@ -617,7 +618,7 @@ public class PeopleSearchServlet extends PwmServlet {
             userInfoBean = null;
         }
         UserDataReader userDataReader = new LdapUserDataReader(userIdentity, chaiUser);
-        return new MacroMachine(pwmApplication, userInfoBean, null, userDataReader);
+        return new MacroMachine(pwmApplication, pwmSession.getLabel(), userInfoBean, null, userDataReader);
     }
 
     private static void checkIfUserIdentityViewable(

+ 1 - 10
pwm/servlet/src/password/pwm/http/servlet/PwmServlet.java

@@ -27,10 +27,7 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.Validator;
 import password.pwm.error.*;
-import password.pwm.http.ContextManager;
-import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmSession;
-import password.pwm.http.PwmSessionWrapper;
+import password.pwm.http.*;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.stats.Statistic;
 import password.pwm.ws.server.RestResultBean;
@@ -264,12 +261,6 @@ public abstract class PwmServlet extends HttpServlet {
         public Collection<HttpMethod> permittedMethods();
     }
 
-    public enum HttpMethod {
-        POST,
-        GET,
-
-    }
-
     public static final Collection<HttpMethod> GET_AND_POST_METHODS;
 
     static {

+ 12 - 10
pwm/servlet/src/password/pwm/http/servlet/SetupOtpServlet.java

@@ -33,6 +33,7 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.ForceSetupPolicy;
 import password.pwm.error.*;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.SetupOtpBean;
@@ -63,24 +64,24 @@ public class SetupOtpServlet extends PwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(SetupOtpServlet.class);
 
     public enum SetupOtpAction implements PwmServlet.ProcessAction {
-        clearOtp(PwmServlet.HttpMethod.POST),
-        testOtpSecret(PwmServlet.HttpMethod.POST),
-        toggleSeen(PwmServlet.HttpMethod.POST),
-        restValidateCode(PwmServlet.HttpMethod.POST),
-        complete(PwmServlet.HttpMethod.POST),
-        showQrImage(PwmServlet.HttpMethod.GET),
-        skip(PwmServlet.HttpMethod.POST),
+        clearOtp(HttpMethod.POST),
+        testOtpSecret(HttpMethod.POST),
+        toggleSeen(HttpMethod.POST),
+        restValidateCode(HttpMethod.POST),
+        complete(HttpMethod.POST),
+        showQrImage(HttpMethod.GET),
+        skip(HttpMethod.POST),
 
         ;
 
-        private final PwmServlet.HttpMethod method;
+        private final HttpMethod method;
 
-        SetupOtpAction(PwmServlet.HttpMethod method)
+        SetupOtpAction(HttpMethod method)
         {
             this.method = method;
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return Collections.singletonList(method);
         }
@@ -396,6 +397,7 @@ public class SetupOtpServlet extends PwmServlet {
                 final OTPUserRecord otpUserRecord = new OTPUserRecord();
                 final List<String> rawRecoveryCodes = pwmApplication.getOtpService().initializeUserRecord(
                         otpUserRecord,
+                        pwmRequest.getSessionLabel(),
                         identifier
                 );
                 otpBean.setOtpUserRecord(otpUserRecord);

+ 10 - 9
pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -43,6 +43,7 @@ import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.UserAuditRecord;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.SetupResponsesBean;
@@ -71,23 +72,23 @@ public class SetupResponsesServlet extends PwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(SetupResponsesServlet.class);
 
     public enum SetupResponsesAction implements PwmServlet.ProcessAction {
-        validateResponses(PwmServlet.HttpMethod.POST),
-        setResponses(PwmServlet.HttpMethod.POST),
-        setHelpdeskResponses(PwmServlet.HttpMethod.POST),
-        confirmResponses(PwmServlet.HttpMethod.POST),
-        clearExisting(PwmServlet.HttpMethod.POST),
-        changeResponses(PwmServlet.HttpMethod.POST),
+        validateResponses(HttpMethod.POST),
+        setResponses(HttpMethod.POST),
+        setHelpdeskResponses(HttpMethod.POST),
+        confirmResponses(HttpMethod.POST),
+        clearExisting(HttpMethod.POST),
+        changeResponses(HttpMethod.POST),
 
         ;
 
-        private final PwmServlet.HttpMethod method;
+        private final HttpMethod method;
 
-        SetupResponsesAction(PwmServlet.HttpMethod method)
+        SetupResponsesAction(HttpMethod method)
         {
             this.method = method;
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return Collections.singletonList(method);
         }

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

@@ -30,6 +30,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.ShortcutItem;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapPermissionTester;
@@ -48,9 +49,9 @@ public class ShortcutServlet extends PwmServlet {
         selectShortcut,
         ;
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
-            return Collections.singletonList(PwmServlet.HttpMethod.GET);
+            return Collections.singletonList(HttpMethod.POST);
         }
     }
 

+ 8 - 7
pwm/servlet/src/password/pwm/http/servlet/UpdateProfileServlet.java

@@ -37,6 +37,7 @@ import password.pwm.config.*;
 import password.pwm.error.*;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.AuditRecord;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.UpdateProfileBean;
@@ -64,22 +65,22 @@ public class UpdateProfileServlet extends PwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(UpdateProfileServlet.class);
 
     public enum UpdateProfileAction implements PwmServlet.ProcessAction {
-        updateProfile(PwmServlet.HttpMethod.POST),
-        agree(PwmServlet.HttpMethod.POST),
-        confirm(PwmServlet.HttpMethod.POST),
-        unConfirm(PwmServlet.HttpMethod.POST),
+        updateProfile(HttpMethod.POST),
+        agree(HttpMethod.POST),
+        confirm(HttpMethod.POST),
+        unConfirm(HttpMethod.POST),
         validate(HttpMethod.POST),
 
         ;
 
-        private final PwmServlet.HttpMethod method;
+        private final HttpMethod method;
 
-        UpdateProfileAction(PwmServlet.HttpMethod method)
+        UpdateProfileAction(HttpMethod method)
         {
             this.method = method;
         }
 
-        public Collection<PwmServlet.HttpMethod> permittedMethods()
+        public Collection<HttpMethod> permittedMethods()
         {
             return Collections.singletonList(method);
         }

+ 1 - 0
pwm/servlet/src/password/pwm/i18n/Admin.properties

@@ -25,6 +25,7 @@
 Header_AdminUser=%1%  You are logged in as administrator.
 Header_ConfigModeActive=%1% is in open configuration mode and is not secure.
 Header_TrialMode=%1% Trial.
+Header_ConfigWarningsPresent=Configuration warnings exist, click to view.
 IntruderRecordType_ADDRESS=Address
 IntruderRecordType_USERNAME=Username
 IntruderRecordType_USER_ID=User DN

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

@@ -107,6 +107,8 @@ Warning_DownloadConfiguration=<b>Warning:</b> The configuration download file co
 Warning_DownloadLocal=<b>Warning:</b> The download LocalDB archive may contain sensitive security information, handle with appropriate care.
 Warning_InvalidFormat=The value does not have the correct format.
 Warning_UploadIE9=This feature is not available when using Internet Explorer 9 and earlier.  Please use a different browser or a newer version of Internet Explorer.
+Warning_ValueIncorrectFormat=The value does not have the correct format.
+Warning_SmsTestData=The test that will be performed will include resolving configured macros (if any).  The macros will be resolved using data of the logged in user, and thus may include sensitive data.
 Tooltip_ResetButton=Return this setting to its default value.
 Tooltip_HelpButton=Show description for this setting.
 Tooltip_Setting_Permission_Profile=Specify which of the defined LDAP profiles to use for the associated filter.  If <i>all</i>, all profiles will be checked for the associated filter.  If <i>default</i>, than only the default LDAP Profile will be checked for the associated search filter.

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

@@ -271,6 +271,7 @@ Title_UserEventHistory=Password History
 Title_UserInformation=My Account
 Tooltip_PasswordStrength=The password strength meter shows how easy it is to guess your password. Try the following to make your password stronger:<ul><li>Make the password longer</li><li>Do not repeat letters or numbers</li><li>Use mixed (upper and lower) case letters</li><li>Add more numbers</li><li>Add more symbol characters</li></ul>
 Confirm_DeleteUser=Are you sure you wish to proceed?  If you continue, the selected user will be deleted permanently.  This can not be undone.
+Confirm=Are you sure you wish to proceed?
 Value_False=False
 Value_True=True
 Value_NotApplicable=n/a

+ 4 - 3
pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java

@@ -254,7 +254,7 @@ public class LdapOperationsHelper {
 
             while (attempts < 10 && newGuid == null) {
                 attempts++;
-                newGuid = generateGuidValue(pwmApplication);
+                newGuid = generateGuidValue(pwmApplication, sessionLabel);
                 if (searchForExistingGuidValue(pwmApplication, sessionLabel, newGuid)) {
                     newGuid = null;
                 }
@@ -280,11 +280,12 @@ public class LdapOperationsHelper {
         }
 
         private static String generateGuidValue(
-                final PwmApplication pwmApplication
+                final PwmApplication pwmApplication,
+                final SessionLabel sessionLabel
         )
                 throws PwmUnrecoverableException
         {
-            final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication);
+            final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, sessionLabel);
             final String guidPattern = pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_GUID_PATTERN);
             return macroMachine.expandMacros(guidPattern);
         }

+ 4 - 1
pwm/servlet/src/password/pwm/PasswordChangeProgressChecker.java → pwm/servlet/src/password/pwm/ldap/PasswordChangeProgressChecker.java

@@ -20,8 +20,11 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm;
+package password.pwm.ldap;
 
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;

+ 4 - 2
pwm/servlet/src/password/pwm/AlertHandler.java → pwm/servlet/src/password/pwm/util/AlertHandler.java

@@ -20,14 +20,16 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-package password.pwm;
+package password.pwm.util;
 
 import org.apache.commons.lang3.text.WordUtils;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.health.HealthRecord;
 import password.pwm.i18n.Display;
-import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.report.ReportSummaryData;
 

+ 0 - 33
pwm/servlet/src/password/pwm/util/Helper.java

@@ -26,18 +26,10 @@ import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.apache.commons.csv.CSVPrinter;
-import org.apache.http.HttpHost;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.HttpClient;
-import org.apache.http.conn.params.ConnRoutePNames;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.params.HttpProtocolParams;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionStateBean;
-import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.ErrorInformation;
@@ -349,31 +341,6 @@ public class
         return -1;
     }
 
-    public static HttpClient getHttpClient(final Configuration configuration)
-    {
-        final DefaultHttpClient httpClient = new DefaultHttpClient();
-
-        final String strValue = configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL);
-        if (strValue != null && strValue.length() > 0) {
-            final URI proxyURI = URI.create(strValue);
-
-            final String host = proxyURI.getHost();
-            final int port = proxyURI.getPort();
-            final HttpHost proxy = new HttpHost(host,port);
-            httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
-
-            final String username = proxyURI.getUserInfo();
-            if (username != null && username.length() > 0) {
-                final String password = (username.contains(":")) ? username.split(":")[1] : "";
-                final UsernamePasswordCredentials passwordCredentials = new UsernamePasswordCredentials(username,password);
-                httpClient.getCredentialsProvider().setCredentials (new AuthScope(host, port),passwordCredentials);
-            }
-        }
-        final String userAgent = PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION;
-        httpClient.getParams().setParameter(HttpProtocolParams.USER_AGENT, userAgent);
-        return httpClient;
-    }
-
     static public String buildPwmFormID(final SessionStateBean ssBean) {
         return ssBean.getSessionVerificationKey() + ssBean.getRequestVerificationKey();
     }

+ 13 - 8
pwm/servlet/src/password/pwm/util/JsonUtil.java

@@ -81,10 +81,11 @@ public class JsonUtil {
     }
 
     public static List<String> deserializeStringList(final String jsonString) {
-        return JsonUtil.getGson().fromJson(jsonString, new TypeToken<List<Object>>() {}.getType());
+        return JsonUtil.getGson().fromJson(jsonString, new TypeToken<List<Object>>() {
+        }.getType());
     }
 
-    public static Map<String,String> deserializeStringMap(final String jsonString) {
+    public static Map<String, String> deserializeStringMap(final String jsonString) {
         return JsonUtil.getGson().fromJson(jsonString, new TypeToken<Map<String, String>>() {
         }.getType());
     }
@@ -123,11 +124,10 @@ public class JsonUtil {
         }
 
         public X509Certificate deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext)
-                throws JsonParseException
-        {
+                throws JsonParseException {
             try {
                 final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
-                return (X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(StringUtil.base64Decode(
+                return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(StringUtil.base64Decode(
                         jsonElement.getAsString())));
             } catch (Exception e) {
                 throw new JsonParseException("unable to parse x509certificate: " + e.getMessage());
@@ -179,7 +179,7 @@ public class JsonUtil {
             } catch (IOException e) {
                 final String errorMsg = "io stream error while deserializing byte array: " + e.getMessage();
                 LOGGER.error(errorMsg);
-                throw new JsonParseException(errorMsg,e);
+                throw new JsonParseException(errorMsg, e);
             }
         }
 
@@ -189,7 +189,7 @@ public class JsonUtil {
             } catch (IOException e) {
                 final String errorMsg = "io stream error while serializing byte array: " + e.getMessage();
                 LOGGER.error(errorMsg);
-                throw new JsonParseException(errorMsg,e);
+                throw new JsonParseException(errorMsg, e);
             }
         }
     }
@@ -200,4 +200,9 @@ public class JsonUtil {
         gsonBuilder.registerTypeAdapter(byte[].class, new ByteArrayToBase64TypeAdapter());
         return gsonBuilder;
     }
-}
+
+    public static <T> T cloneUsingJson(final Serializable srcObject, final Class<T> classOfT) {
+        final String asJson = JsonUtil.serialize(srcObject);
+        return JsonUtil.deserialize(asJson, classOfT);
+    }
+}

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

@@ -46,6 +46,8 @@ public class PasswordData {
     private static final String staticKeyHash;
     private static final ErrorInformation initializationError;
 
+    private String passwordHashCache;
+
     static {
         SecretKey newKey = null;
         String newKeyHash = null;
@@ -148,4 +150,11 @@ public class PasswordData {
                 ? null
                 : new PasswordData(input);
     }
+
+    public String hash() throws PwmUnrecoverableException {
+        if (passwordHashCache == null) {
+            passwordHashCache = SecureHelper.hash(this.getStringValue());
+        }
+        return passwordHashCache;
+    }
 }

+ 2 - 1
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.http.client.PwmHttpClient;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Properties;
@@ -48,7 +49,7 @@ public class TinyUrlShortener extends BasicUrlShortener {
 			LOGGER.debug("Trying to shorten url: "+input);
 			final String encodedUrl = StringUtil.urlEncode(input);
 			final String callUrl = apiUrl + encodedUrl;
-        	final HttpClient httpClient = Helper.getHttpClient(context.getConfig());
+        	final HttpClient httpClient = PwmHttpClient.getHttpClient(context.getConfig());
 	        final HttpGet httpRequest = new HttpGet(callUrl);
     	    final HttpResponse httpResponse = httpClient.execute(httpRequest);
     	    final int httpResponseCode = httpResponse.getStatusLine().getStatusCode();

+ 32 - 3
pwm/servlet/src/password/pwm/util/logging/PwmLogger.java

@@ -24,12 +24,15 @@ package password.pwm.util.logging;
 
 import org.apache.log4j.RollingFileAppender;
 import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.event.AuditEvent;
 import password.pwm.event.SystemAuditRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
+import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.util.JsonUtil;
 
 import java.io.IOException;
@@ -107,14 +110,22 @@ public class PwmLogger {
 
     private void doPwmRequestLogEvent(final PwmLogLevel level, final PwmRequest pwmRequest, final Object message, final Throwable e)
     {
-        final SessionLabel sessionLabel = pwmRequest != null ? pwmRequest.getSessionLabel() : null;
-        doLogEvent(level, sessionLabel, message, e);
+        final PwmSession pwmSession = pwmRequest != null ? pwmRequest.getPwmSession() : null;
+        doPwmSessionLogEvent(level, pwmSession, message, e);
     }
 
     private void doPwmSessionLogEvent(final PwmLogLevel level, final PwmSession pwmSession, final Object message, final Throwable e)
     {
         final SessionLabel sessionLabel = pwmSession != null ? pwmSession.getLabel() : null;
-        doLogEvent(level, sessionLabel, message, e);
+        Object cleanedMessage = message;
+        if (pwmSession != null && message != null) {
+            try {
+                cleanedMessage = PwmLogger.removeUserDataFromString(pwmSession.getLoginInfoBean(), message.toString());
+            } catch (PwmUnrecoverableException e1) {
+                /* can't be logged */
+            }
+        };
+        doLogEvent(level, sessionLabel, cleanedMessage, e);
     }
 
     private void doLogEvent(final PwmLogLevel level, final SessionLabel sessionLabel, final Object message, final Throwable e)
@@ -436,5 +447,23 @@ public class PwmLogger {
             }
         }
     }
+
+    public static String removeUserDataFromString(final LoginInfoBean loginInfoBean, final String input)
+            throws PwmUnrecoverableException
+    {
+        if (input == null || loginInfoBean == null) {
+            return input;
+        }
+
+        String returnString = input;
+        if (loginInfoBean.getUserCurrentPassword() != null) {
+            final String pwdStringValue = loginInfoBean.getUserCurrentPassword().getStringValue();
+            if (pwdStringValue != null && !pwdStringValue.isEmpty() && returnString.contains(pwdStringValue)) {
+                returnString = returnString.replace(pwdStringValue, PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT);
+            }
+        }
+
+        return returnString;
+    }
 }
 

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

@@ -25,4 +25,9 @@ package password.pwm.util.macro;
 public abstract class AbstractMacro implements MacroImplementation {
     public AbstractMacro() {
     }
+
+    @Override
+    public boolean isSensitive() {
+        return false;
+    }
 }

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

@@ -41,4 +41,6 @@ public interface MacroImplementation {
         LoginInfoBean getLoginInfoBean();
         UserDataReader getUserDataReader();
     }
+
+    boolean isSensitive();
 }

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

@@ -45,6 +45,7 @@ public class MacroMachine {
     private static final PwmLogger LOGGER = PwmLogger.forClass(MacroMachine.class);
 
     private final PwmApplication pwmApplication;
+    private final SessionLabel sessionLabel;
     private final UserInfoBean userInfoBean;
     private final LoginInfoBean loginInfoBean;
     private final UserDataReader userDataReader;
@@ -52,12 +53,14 @@ public class MacroMachine {
 
     public MacroMachine(
             final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
             final UserInfoBean userInfoBean,
             final LoginInfoBean loginInfoBean,
             final UserDataReader userDataReader
     )
     {
         this.pwmApplication = pwmApplication;
+        this.sessionLabel = sessionLabel;
         this.userInfoBean = userInfoBean;
         this.loginInfoBean = loginInfoBean;
         this.userDataReader = userDataReader;
@@ -76,7 +79,7 @@ public class MacroMachine {
                 final Pattern pattern = macroImplementation.getRegExPattern();
                 map.put(pattern,macroImplementation);
             } catch (Exception e) {
-                LOGGER.error("unable to load macro class " + macroClass.getName() + ", error: " + e.getMessage());
+                LOGGER.error(sessionLabel, "unable to load macro class " + macroClass.getName() + ", error: " + e.getMessage());
             }
         }
 
@@ -173,10 +176,10 @@ public class MacroMachine {
         try {
             replaceStr = macroImplementation.replaceValue(matchedStr, macroRequestInfo);
         } catch (MacroParseException e) {
-            LOGGER.debug("macro parse error replacing macro '" + matchedStr + "', error: " + e.getMessage());
+            LOGGER.debug(sessionLabel, "macro parse error replacing macro '" + matchedStr + "', error: " + e.getMessage());
             replaceStr = "[" + e.getErrorInformation().toUserStr(PwmConstants.DEFAULT_LOCALE,macroRequestInfo.getPwmApplication().getConfig()) + "]";
         }  catch (Exception e) {
-            LOGGER.error("error while replacing macro '" + matchedStr + "', error: " + e.getMessage());
+            LOGGER.error(sessionLabel, "error while replacing macro '" + matchedStr + "', error: " + e.getMessage());
         }
 
         if (replaceStr == null) {
@@ -187,12 +190,13 @@ public class MacroMachine {
             try {
                 replaceStr = stringReplacer.replace(matchedStr, replaceStr);
             }  catch (Exception e) {
-                LOGGER.error("unexpected error while executing '" + matchedStr + "' during StringReplacer.replace(), error: " + e.getMessage());
+                LOGGER.error(sessionLabel,"unexpected error while executing '" + matchedStr + "' during StringReplacer.replace(), error: " + e.getMessage());
             }
         }
 
         if (replaceStr != null && replaceStr.length() > 0) {
-            LOGGER.trace("replaced macro " + matchedStr + " with value: " + replaceStr);
+            LOGGER.trace(sessionLabel, "replaced macro " + matchedStr + " with value: "
+                    + (macroImplementation.isSensitive() ? PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT : replaceStr));
         }
         return new StringBuilder(input).replace(startPos, endPos, replaceStr).toString();
     }
@@ -228,15 +232,16 @@ public class MacroMachine {
         final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, sessionLabel);
         final UserInfoBean userInfoBean = new UserInfoBean();
         userStatusReader.populateUserInfoBean(userInfoBean, userLocale, userIdentity);
-        return new MacroMachine(pwmApplication, null, null, userDataReader);
+        return new MacroMachine(pwmApplication, sessionLabel, null, null, userDataReader);
     }
 
     public static MacroMachine forNonUserSpecific(
-            final PwmApplication pwmApplication
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel
     )
             throws PwmUnrecoverableException
     {
-        return new MacroMachine(pwmApplication, null, null, null);
+        return new MacroMachine(pwmApplication, sessionLabel, null, null, null);
     }
 
 }

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

@@ -404,6 +404,11 @@ public abstract class StandardMacros {
                 return "";
             }
         }
+
+        @Override
+        public boolean isSensitive() {
+            return true;
+        }
     }
 
     public static class SiteURLMacro extends AbstractMacro {

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

@@ -25,15 +25,6 @@ package password.pwm.util.operations;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.util.EntityUtils;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.ActionConfiguration;
@@ -41,13 +32,17 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmSession;
-import password.pwm.util.Helper;
+import password.pwm.http.client.PwmHttpClient;
+import password.pwm.http.client.PwmHttpClientRequest;
+import password.pwm.http.client.PwmHttpClientResponse;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 
-import java.net.URI;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 public class ActionExecutor {
 
@@ -80,18 +75,22 @@ public class ActionExecutor {
     {
         switch (actionConfiguration.getType()) {
             case ldap:
-                executeLdapAction(actionConfiguration, actionExecutorSettings);
+                executeLdapAction(pwmSession, actionConfiguration, actionExecutorSettings);
                 break;
 
             case webservice:
-                executeWebserviceAction(actionConfiguration, actionExecutorSettings);
+                executeWebserviceAction(pwmSession, actionConfiguration, actionExecutorSettings);
                 break;
         }
 
         LOGGER.info(pwmSession,"action " + actionConfiguration.getName() + " completed successfully");
     }
 
-    private void executeLdapAction(final ActionConfiguration actionConfiguration, final ActionExecutorSettings settings)
+    private void executeLdapAction(
+            final PwmSession pwmSession,
+            final ActionConfiguration actionConfiguration,
+            final ActionExecutorSettings settings
+    )
             throws ChaiUnavailableException, PwmOperationalException, PwmUnrecoverableException
     {
         final String attributeName = actionConfiguration.getAttributeName();
@@ -101,6 +100,7 @@ public class ActionExecutor {
                 pwmApplication.getProxiedChaiUser(settings.getUserIdentity());
 
         writeLdapAttribute(
+                pwmSession,
                 theUser,
                 attributeName,
                 attributeValue,
@@ -110,6 +110,7 @@ public class ActionExecutor {
     }
 
     private void executeWebserviceAction(
+            final PwmSession pwmSession,
             final ActionConfiguration actionConfiguration,
             final ActionExecutorSettings settings
     )
@@ -117,62 +118,48 @@ public class ActionExecutor {
     {
         String url = actionConfiguration.getUrl();
         String body = actionConfiguration.getBody();
-        final MacroMachine macroMachine = settings.getMacroMachine();
+        final Map<String,String> headers = new LinkedHashMap<>();
+        if (actionConfiguration.getHeaders() != null) {
+            headers.putAll(actionConfiguration.getHeaders());
+        }
 
         try {
             // expand using pwm macros
             if (settings.isExpandPwmMacros()) {
-                url = macroMachine.expandMacros(url, new MacroMachine.URLEncoderReplacer());
-                body = body == null ? "" : macroMachine.expandMacros(body, new MacroMachine.URLEncoderReplacer());
-            }
+                if (settings.getMacroMachine() == null) {
+                    throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"executor specified macro expansion but did not supply macro machine"));
+                }
+                final MacroMachine macroMachine = settings.getMacroMachine();
 
-            LOGGER.debug("sending HTTP request: " + url);
-            final URI requestURI = new URI(url);
-            final HttpRequestBase httpRequest;
-            switch (actionConfiguration.getMethod()) {
-                case post:
-                    httpRequest = new HttpPost(requestURI.toString());
-                    ((HttpPost)httpRequest).setEntity(new StringEntity(body));
-                    break;
-
-                case put:
-                    httpRequest = new HttpPut(requestURI.toString());
-                    ((HttpPut)httpRequest).setEntity(new StringEntity(body));
-                    break;
-
-                case get:
-                    httpRequest = new HttpGet(requestURI.toString());
-                    break;
-
-                case delete:
-                    httpRequest = new HttpGet(requestURI.toString());
-                    break;
-
-                default:
-                    throw new IllegalStateException("method not yet implemented");
-            }
+                url = macroMachine.expandMacros(url, new MacroMachine.URLEncoderReplacer());
+                if (actionConfiguration.getBodyEncoding() == ActionConfiguration.BodyEncoding.url) {
+                    body = body == null ? "" : macroMachine.expandMacros(body, new MacroMachine.URLEncoderReplacer());
+                } else {
+                    body = body == null ? "" : macroMachine.expandMacros(body);
+                }
 
-            if (actionConfiguration.getHeaders() != null) {
-                for (final String headerName : actionConfiguration.getHeaders().keySet()) {
-                    String headerValue = actionConfiguration.getHeaders().get(headerName);
-                    headerValue = headerValue == null ? "" : macroMachine.expandMacros(headerValue);
-                    httpRequest.setHeader(headerName,headerValue);
+                for (final String headerName : headers.keySet()) {
+                    final String headerValue = headers.get(headerName);
+                    if (headerValue != null) {
+                        headers.put(headerName, macroMachine.expandMacros(headerValue));
+                    }
                 }
             }
 
-            final HttpClient httpClient = Helper.getHttpClient(pwmApplication.getConfig());
-            final HttpResponse httpResponse = httpClient.execute(httpRequest);
-            if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
-                throw new PwmOperationalException(new ErrorInformation(
-                        PwmError.ERROR_UNKNOWN,
-                        "unexpected HTTP status code while calling external web service: "
-                                + httpResponse.getStatusLine().getStatusCode() + " " + httpResponse.getStatusLine().getReasonPhrase()
-                ));
-            }
+            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 PwmHttpClientResponse clientResponse = client.makeRequest(clientRequest);
+
+                if (clientResponse.getStatusCode() != 200) {
+                    throw new PwmOperationalException(new ErrorInformation(
+                            PwmError.ERROR_SERVICE_UNREACHABLE,
+                            "unexpected HTTP status code while calling external web service: "
+                                    + clientResponse.getStatusCode() + " " + clientResponse.getStatusPhrase()
+                    ));
+                }
 
-            final String responseBody = EntityUtils.toString(httpResponse.getEntity());
-            LOGGER.debug("response from http rest request: " + httpResponse.getStatusLine());
-            LOGGER.trace("response body from http rest request: " + responseBody);
         } catch (Exception e) {
             if (e instanceof PwmOperationalException) {
                 throw (PwmOperationalException)e;
@@ -185,6 +172,7 @@ public class ActionExecutor {
     }
 
     private static void writeLdapAttribute(
+            final PwmSession pwmSession,
             final ChaiUser theUser,
             final String attrName,
             String attrValue,
@@ -201,13 +189,13 @@ public class ActionExecutor {
             attrValue  = macroMachine.expandMacros(attrValue);
         }
 
-        LOGGER.trace("beginning ldap " + ldapMethod.toString() + " operation on " + theUser.getEntryDN() + ", attribute " + attrName);
+        LOGGER.trace(pwmSession,"beginning ldap " + ldapMethod.toString() + " operation on " + theUser.getEntryDN() + ", attribute " + attrName);
         switch (ldapMethod) {
             case replace:
             {
                 try {
                     theUser.writeStringAttribute(attrName, attrValue);
-                    LOGGER.info("replaced attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + attrValue + ")");
+                    LOGGER.info(pwmSession,"replaced attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + attrValue + ")");
                 } catch (ChaiOperationException e) {
                     final String errorMsg = "error setting '" + attrName + "' attribute on user " + theUser.getEntryDN() + ", error: " + e.getMessage();
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
@@ -222,7 +210,7 @@ public class ActionExecutor {
             {
                 try {
                     theUser.addAttribute(attrName, attrValue);
-                    LOGGER.info("added attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + attrValue + ")");
+                    LOGGER.info(pwmSession,"added attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + attrValue + ")");
                 } catch (ChaiOperationException e) {
                     final String errorMsg = "error adding '" + attrName + "' attribute value from user " + theUser.getEntryDN() + ", error: " + e.getMessage();
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
@@ -238,7 +226,7 @@ public class ActionExecutor {
             {
                 try {
                     theUser.deleteAttribute(attrName, attrValue);
-                    LOGGER.info("deleted attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")");
+                    LOGGER.info(pwmSession,"deleted attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")");
                 } catch (ChaiOperationException e) {
                     final String errorMsg = "error deletig '" + attrName + "' attribute value on user " + theUser.getEntryDN() + ", error: " + e.getMessage();
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
@@ -254,8 +242,6 @@ public class ActionExecutor {
         }
     }
 
-
-
     public static class ActionExecutorSettings {
         private MacroMachine macroMachine;
         private ChaiUser chaiUser;
@@ -300,4 +286,6 @@ public class ActionExecutor {
             this.userIdentity = userIdentity;
         }
     }
+
+
 }

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

@@ -132,10 +132,10 @@ public class OtpService implements PwmService {
         return otpCorrect;
     }
 
-    private List<String> createRawRecoveryCodes(final int numRecoveryCodes)
+    private List<String> createRawRecoveryCodes(final int numRecoveryCodes, final SessionLabel sessionLabel)
             throws PwmUnrecoverableException 
     {
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication);
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, sessionLabel);
         final String configuredTokenMacro = settings.getRecoveryTokenMacro();
         final List<String> recoveryCodes = new ArrayList<>();
         while (recoveryCodes.size() < numRecoveryCodes) {
@@ -147,6 +147,7 @@ public class OtpService implements PwmService {
 
     public List<String> initializeUserRecord(
             final OTPUserRecord otpUserRecord,
+            final SessionLabel sessionLabel,
             String identifier
     )
             throws IOException, PwmUnrecoverableException {
@@ -167,16 +168,16 @@ public class OtpService implements PwmService {
         }
         final List<String> rawRecoveryCodes;
         if (settings.getOtpStorageFormat().supportsRecoveryCodes()) {
-            rawRecoveryCodes = createRawRecoveryCodes(settings.getRecoveryCodesCount());
+            rawRecoveryCodes = createRawRecoveryCodes(settings.getRecoveryCodesCount(), sessionLabel);
             final List<OTPUserRecord.RecoveryCode> recoveryCodeList = new ArrayList<>();
             final OTPUserRecord.RecoveryInfo recoveryInfo = new OTPUserRecord.RecoveryInfo();
             if (settings.getOtpStorageFormat().supportsHashedRecoveryCodes()) {
-                LOGGER.debug("Hashing the recovery codes");
+                LOGGER.trace(sessionLabel, "hashing the recovery codes");
                 recoveryInfo.setSalt(PwmRandom.getInstance().alphaNumericString(32));
                 recoveryInfo.setHashCount(settings.getRecoveryHashIterations());
                 recoveryInfo.setHashMethod(settings.getRecoveryHashMethod());
             } else {
-                LOGGER.debug("Not hashing the recovery codes");
+                LOGGER.trace(sessionLabel, "not hashing the recovery codes");
                 recoveryInfo.setSalt(null);
                 recoveryInfo.setHashCount(0);
                 recoveryInfo.setHashMethod(null);

+ 53 - 25
pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java

@@ -33,6 +33,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import com.novell.ldapchai.provider.ChaiProviderFactory;
 import com.novell.ldapchai.provider.ChaiSetting;
 import com.novell.ldapchai.util.ChaiUtility;
+import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -82,8 +83,6 @@ public class PasswordUtility {
     )
             throws PwmOperationalException, PwmUnrecoverableException
     {
-        final Configuration config = pwmApplication.getConfig();
-
         final String emailAddress = userInfoBean.getUserEmailAddress();
         final String smsNumber = userInfoBean.getUserSmsNumber();
         String returnToAddress = emailAddress;
@@ -352,11 +351,26 @@ public class PasswordUtility {
         {  // execute configured actions
             LOGGER.debug(pwmSession, "executing configured actions to user " + proxiedUser.getEntryDN());
             final List<ActionConfiguration> configValues = pwmApplication.getConfig().readSettingAsAction(PwmSetting.CHANGE_PASSWORD_WRITE_ATTRIBUTES);
-            final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-            settings.setExpandPwmMacros(true);
-            settings.setUserIdentity(uiBean.getUserIdentity());
-            final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-            actionExecutor.executeActions(configValues, settings, pwmSession);
+            if (configValues != null && !configValues.isEmpty()) {
+                final LoginInfoBean clonedLoginInfoBean = JsonUtil.cloneUsingJson(pwmSession.getLoginInfoBean(), LoginInfoBean.class);
+                clonedLoginInfoBean.setUserCurrentPassword(newPassword);
+
+                final MacroMachine macroMachine = new MacroMachine(
+                        pwmApplication,
+                        pwmSession.getLabel(),
+                        pwmSession.getUserInfoBean(),
+                        clonedLoginInfoBean,
+                        pwmSession.getSessionManager().getUserDataReader(pwmApplication)
+                );
+
+                final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
+                settings.setExpandPwmMacros(true);
+                settings.setUserIdentity(uiBean.getUserIdentity());
+                settings.setMacroMachine(macroMachine);
+
+                final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
+                actionExecutor.executeActions(configValues, settings, pwmSession);
+            }
         }
 
         //update the current last password update field in ldap
@@ -442,6 +456,12 @@ public class PasswordUtility {
             if (!actions.isEmpty()) {
                 final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
                 settings.setExpandPwmMacros(true);
+                settings.setMacroMachine(MacroMachine.forUser(
+                        pwmApplication,
+                        pwmSession.getSessionStateBean().getLocale(),
+                        sessionLabel,
+                        userIdentity
+                ));
                 settings.setUserIdentity(userIdentity);
                 final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
                 actionExecutor.executeActions(actions,settings,pwmSession);
@@ -475,8 +495,7 @@ public class PasswordUtility {
             final UserDataReader userDataReader = new LdapUserDataReader(userIdentity, chaiUser);
             final LoginInfoBean loginInfoBean = new LoginInfoBean();
             loginInfoBean.setUserCurrentPassword(newPassword);
-            final MacroMachine macroMachine = new MacroMachine(pwmApplication, userInfoBean, loginInfoBean, userDataReader);
-
+            final MacroMachine macroMachine = new MacroMachine(pwmApplication, pwmSession.getLabel(), userInfoBean, loginInfoBean, userDataReader);
             PasswordUtility.sendNewPassword(
                     userInfoBean,
                     pwmApplication,
@@ -532,7 +551,7 @@ public class PasswordUtility {
     {
         final List<PostChangePasswordAction> postChangePasswordActions = pwmSession.getLoginInfoBean().removePostChangePasswordActions();
         if (postChangePasswordActions == null || postChangePasswordActions.isEmpty()) {
-            LOGGER.trace("no post change password actions ");
+            LOGGER.trace(pwmSession, "no post change password actions pending from previous operations");
             return;
         }
 
@@ -671,8 +690,8 @@ public class PasswordUtility {
             final UserIdentity userIdentity,
             final ChaiUser theUser,
             final Locale locale
-    ) 
-    throws PwmUnrecoverableException
+    )
+            throws PwmUnrecoverableException
     {
         final long startTime = System.currentTimeMillis();
         final PasswordPolicySource ppSource = PasswordPolicySource.valueOf(pwmApplication.getConfig().readSettingAsString(PwmSetting.PASSWORD_POLICY_SOURCE));
@@ -716,17 +735,17 @@ public class PasswordUtility {
         }
 
         for (final String profile : profiles) {
-                final PwmPasswordPolicy loopPolicy = pwmApplication.getConfig().getPasswordPolicy(profile,locale);
-                final List<UserPermission> userPermissions = loopPolicy.getUserPermissions();
-                LOGGER.debug(pwmSession, "testing password policy profile '" + profile + "'");
-                try {
-                    boolean match = LdapPermissionTester.testUserPermissions(pwmApplication, pwmSession, userIdentity, userPermissions);
-                    if (match) {
-                        return loopPolicy;
-                    }
-                } catch (PwmUnrecoverableException e) {
-                    LOGGER.error(pwmSession,"unexpected error while testing password policy profile '" + profile + "', error: " + e.getMessage());
+            final PwmPasswordPolicy loopPolicy = pwmApplication.getConfig().getPasswordPolicy(profile,locale);
+            final List<UserPermission> userPermissions = loopPolicy.getUserPermissions();
+            LOGGER.debug(pwmSession, "testing password policy profile '" + profile + "'");
+            try {
+                boolean match = LdapPermissionTester.testUserPermissions(pwmApplication, pwmSession, userIdentity, userPermissions);
+                if (match) {
+                    return loopPolicy;
                 }
+            } catch (PwmUnrecoverableException e) {
+                LOGGER.error(pwmSession,"unexpected error while testing password policy profile '" + profile + "', error: " + e.getMessage());
+            }
         }
 
         throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_NO_PROFILE_ASSIGNED,"no challenge profile is configured"));
@@ -785,16 +804,25 @@ public class PasswordUtility {
         int errorCode = 0;
 
         final boolean passwordIsCaseSensitive = userInfoBean.getPasswordPolicy() == null || userInfoBean.getPasswordPolicy().getRuleHelper().readBooleanValue(PwmPasswordRule.CaseSensitive);
-        final CachePolicy cachePolicy = CachePolicy.makePolicy(30 * 1000);
+        final CachePolicy cachePolicy;
+        {
+            final long cacheLifetimeMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_PWRULECHECK_LIFETIME_MS));
+            cachePolicy = CachePolicy.makePolicy(cacheLifetimeMS);
+        }
 
         if (password == null) {
             userMessage = new ErrorInformation(PwmError.PASSWORD_MISSING).toUserStr(locale, pwmApplication.getConfig());
         } else {
             final CacheService cacheService = pwmApplication.getCacheService();
             final CacheKey cacheKey = user != null && userInfoBean.getUserIdentity() != null
-                    ? CacheKey.makeCacheKey(PasswordUtility.class, userInfoBean.getUserIdentity(), user.getEntryDN() + ":" + password )
+                    ? CacheKey.makeCacheKey(
+                    PasswordUtility.class,
+                    userInfoBean.getUserIdentity(),
+                    user.getEntryDN() + ":" + password.hash())
                     : null;
-
+            if (pwmApplication.getConfig().isDevDebugMode()) {
+                LOGGER.trace("generated cacheKey for password check request: " + cacheKey);
+            }
             try {
                 if (cacheService != null && cacheKey != null) {
                     final String cachedValue = cacheService.get(cacheKey);

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

@@ -38,6 +38,7 @@ import password.pwm.config.PwmSetting;
 import password.pwm.error.*;
 import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
+import password.pwm.http.client.PwmHttpClient;
 import password.pwm.util.*;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.logging.PwmLogger;
@@ -448,7 +449,7 @@ public class SmsQueueManager extends AbstractQueueManager {
                     httpRequest.addHeader(PwmConstants.HttpHeader.Authorization.getHttpName(), ba.toAuthHeader());
                 }
 
-                final HttpClient httpClient = Helper.getHttpClient(config);
+                final HttpClient httpClient = PwmHttpClient.getHttpClient(config);
                 final HttpResponse httpResponse = httpClient.execute(httpRequest);
                 final String responseBody = EntityUtils.toString(httpResponse.getEntity());
                 final int resultCode = httpResponse.getStatusLine().getStatusCode();

+ 3 - 6
pwm/servlet/src/password/pwm/util/stats/StatisticsManager.java

@@ -27,7 +27,6 @@ import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
-import password.pwm.AlertHandler;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmService;
@@ -38,10 +37,8 @@ import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
-import password.pwm.util.Helper;
-import password.pwm.util.JsonUtil;
-import password.pwm.util.PwmRandom;
-import password.pwm.util.TimeDuration;
+import password.pwm.http.client.PwmHttpClient;
+import password.pwm.util.*;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.logging.PwmLogger;
@@ -504,7 +501,7 @@ public class StatisticsManager implements PwmService {
         httpPost.setHeader("Accept", PwmConstants.AcceptValue.json.getHeaderValue());
         httpPost.setHeader("Content-Type", PwmConstants.ContentTypeValue.json.getHeaderValue());
         LOGGER.debug("preparing to send anonymous statistics to " + requestURI.toString() + ", data to send: " + jsonDataString);
-        final HttpResponse httpResponse = Helper.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
+        final HttpResponse httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
         if (httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
             throw new IOException("http response error code: " + httpResponse.getStatusLine().getStatusCode());
         }

+ 2 - 2
pwm/servlet/src/password/pwm/ws/client/rest/RestClientHelper.java

@@ -31,7 +31,7 @@ import password.pwm.PwmConstants;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
-import password.pwm.util.Helper;
+import password.pwm.http.client.PwmHttpClient;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.IOException;
@@ -60,7 +60,7 @@ public class RestClientHelper {
             stringEntity.setContentType(PwmConstants.AcceptValue.json.getHeaderValue());
             httpPost.setEntity(stringEntity);
             LOGGER.debug("beginning external rest call to: " + httpPost.toString() + ", body: " + jsonRequestBody);
-            httpResponse = Helper.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
+            httpResponse = PwmHttpClient.getHttpClient(pwmApplication.getConfig()).execute(httpPost);
             final String responseBody = EntityUtils.toString(httpResponse.getEntity());
             LOGGER.trace("external rest call returned: " + httpResponse.getStatusLine().toString() + ", body: " + responseBody);
             if (httpResponse.getStatusLine().getStatusCode() != 200) {

+ 7 - 0
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-attributes.jsp

@@ -64,6 +64,12 @@ this is handled this way so on browsers where hiding fields is not possible, the
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
                     <pwm:display key="Button_RecoverPassword"/>
                 </button>
+                <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
+                <button type="button" id="button-goBack" name="button-goBack" class="btn" >
+                    <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                    <pwm:display key="Button_GoBack"/>
+                </button>
+                <% } %>
                 <%@ include file="/WEB-INF/jsp/fragment/button-reset.jsp" %>
                 <%@ include file="/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp" %>
                 <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
@@ -79,6 +85,7 @@ this is handled this way so on browsers where hiding fields is not possible, the
             PWM_MAIN.addEventHandler('button_cancel', 'click', function(event){
                 PWM_MAIN.handleFormSubmit(PWM_MAIN.getObject('button_cancel'),event);
             });
+            PWM_MAIN.submitPostAction('button-goBack','ForgottenPassword','<%=ForgottenPasswordServlet.ForgottenPasswordAction.verificationChoice%>');
         });
     </script>
 </pwm:script>

+ 13 - 0
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-enterotp.jsp

@@ -53,6 +53,12 @@
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
                     <pwm:display key="Button_CheckCode"/>
                 </button>
+                <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
+                <button type="button" id="button-goBack" name="button-goBack" class="btn" >
+                    <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                    <pwm:display key="Button_GoBack"/>
+                </button>
+                <% } %>
                 <%@ include file="/WEB-INF/jsp/fragment/button-reset.jsp" %>
                 <%@ include file="/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp" %>
                 <input type="hidden" id="processAction" name="processAction" value="enterOtp"/>
@@ -62,6 +68,13 @@
     </div>
     <div class="push"></div>
 </div>
+<pwm:script>
+    <script>
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_MAIN.submitPostAction('button-goBack','ForgottenPassword','<%=ForgottenPasswordServlet.ForgottenPasswordAction.verificationChoice%>');
+        });
+    </script>
+</pwm:script>
 <%@ include file="fragment/footer.jsp" %>
 </body>
 </html>

+ 14 - 2
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-entertoken.jsp

@@ -23,7 +23,6 @@
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html; charset=UTF-8" %>
 <%@ page import="password.pwm.http.bean.ForgottenPasswordBean" %>
-<%@ page import="password.pwm.http.servlet.ForgottenPasswordServlet" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ include file="fragment/header.jsp" %>
 <html dir="<pwm:LocaleOrientation/>">
@@ -38,7 +37,7 @@
             String destination = fpb.getProgress().getTokenSentAddress();
         %>
         <p><pwm:display key="Display_RecoverEnterCode" value1="<%=destination%>"/></p>
-        <form action="<pwm:url url='../public/ForgottenPassword'/>" method="post"
+        <form action="<pwm:url url='../public/ForgottenPassword'/>" method="post"4
               enctype="application/x-www-form-urlencoded" name="search" class="pwm-form">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <h2><label for="<%=PwmConstants.PARAM_TOKEN%>"><pwm:display key="Field_Code"/></label></h2>
@@ -48,6 +47,12 @@
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
                     <pwm:display key="Button_CheckCode"/>
                 </button>
+                <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
+                <button type="button" id="button-goBack" name="button-goBack" class="btn" >
+                    <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                    <pwm:display key="Button_GoBack"/>
+                </button>
+                <% } %>
                 <%@ include file="/WEB-INF/jsp/fragment/button-reset.jsp" %>
                 <input type="hidden" id="processAction" name="processAction" value="<%=ForgottenPasswordServlet.ForgottenPasswordAction.enterCode%>"/>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
@@ -57,6 +62,13 @@
     </div>
     <div class="push"></div>
 </div>
+<pwm:script>
+    <script>
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_MAIN.submitPostAction('button-goBack','ForgottenPassword','<%=ForgottenPasswordServlet.ForgottenPasswordAction.verificationChoice%>');
+        });
+    </script>
+</pwm:script>
 <%@ include file="fragment/footer.jsp" %>
 </body>
 </html>

+ 1 - 2
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-method.jsp

@@ -1,4 +1,3 @@
-<%@ page import="password.pwm.http.servlet.ForgottenPasswordServlet" %>
 <%@ page import="java.util.HashSet" %>
 <%@ page import="java.util.Set" %>
 <%--
@@ -51,7 +50,7 @@
             <tr>
                 <td>
                     <form action="<pwm:url url='ForgottenPassword'/>" method="post"
-                          enctype="application/x-www-form-urlencoded" name="search">
+                          enctype="application/x-www-form-urlencoded" class="pwm-form" id="form-<%=method.toString()%>">
                         <button class="btn" type="submit" name="submitBtn">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-forward"></span></pwm:if>
                             <%=method.getLabel(pwmRequest.getConfig(),pwmRequest.getLocale())%>

+ 13 - 0
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-responses.jsp

@@ -61,6 +61,12 @@ this is handled this way so on browsers where hiding fields is not possible, the
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
                     <pwm:display key="Button_RecoverPassword"/>
                 </button>
+                <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
+                <button type="button" id="button-goBack" name="button-goBack" class="btn" >
+                    <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                    <pwm:display key="Button_GoBack"/>
+                </button>
+                <% } %>
                 <%@ include file="/WEB-INF/jsp/fragment/button-reset.jsp" %>
                 <%@ include file="/WEB-INF/jsp/fragment/forgottenpassword-cancel.jsp" %>
                 <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
@@ -69,6 +75,13 @@ this is handled this way so on browsers where hiding fields is not possible, the
     </div>
     <div class="push"></div>
 </div>
+<pwm:script>
+    <script>
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_MAIN.submitPostAction('button-goBack','ForgottenPassword','<%=ForgottenPasswordServlet.ForgottenPasswordAction.verificationChoice%>');
+        });
+    </script>
+</pwm:script>
 <pwm:script-ref url="/public/resources/js/responses.js"/>
 <%@ include file="fragment/footer.jsp" %>
 </body>

+ 1 - 0
pwm/servlet/web/WEB-INF/jsp/fragment/header-warnings.jsp

@@ -62,6 +62,7 @@
 %>
 <% if (includeHeader) { %>
 <pwm:script-ref url="/public/resources/js/configmanager.js"/>
+<pwm:script-ref url="/public/resources/js/admin.js"/>
 <pwm:script>
     <script type="text/javascript">
         PWM_GLOBAL['startupFunctions'].push(function(){

+ 33 - 38
pwm/servlet/web/WEB-INF/jsp/helpdesk-detail.jsp

@@ -60,6 +60,7 @@
     final UserInfoBean searchedUserInfo = helpdeskBean.getUserInfoBean();
     final String displayName = helpdeskBean.getUserDisplayName();
     final Set<ViewStatusFields> viewStatusFields = helpdeskProfile.readSettingAsOptionList(PwmSetting.HELPDESK_VIEW_STATUS_VALUES,ViewStatusFields.class);
+    final boolean hasOtp = searchedUserInfo.getOtpUserRecord() != null;
 %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
@@ -551,6 +552,31 @@
                         </pwm:script>
                         <br/>
                         <% } %>
+                        <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE_UNLOCK)) { %>
+                        <% if (helpdeskBean.getAdditionalUserInfo().isIntruderLocked()) { %>
+                        <button id="helpdesk_unlockBtn" class="btn" style="width:150px">
+                            <pwm:if test="showIcons"><span class="btn-icon fa fa-unlock"></span></pwm:if>
+                            <pwm:display key="Button_Unlock"/>
+                        </button>
+                        <pwm:script>
+                            <script type="text/javascript">
+                                PWM_GLOBAL['startupFunctions'].push(function(){
+                                    PWM_MAIN.addEventHandler('helpdesk_unlockBtn','click',function(){
+                                        PWM_MAIN.showConfirmDialog({okAction:function() {
+                                            document.ldapUnlockForm.submit();
+                                        }});
+                                    });
+                                });
+                            </script>
+                        </pwm:script>
+                        <% } else { %>
+                        <button id="helpdesk_unlockBtn" class="btn" disabled="disabled" style="width:150px">
+                            <pwm:if test="showIcons"><span class="btn-icon fa fa-unlock"></span></pwm:if>
+                            <pwm:display key="Button_Unlock"/>
+                        </button>
+                        <% } %>
+                        <br/>
+                        <% } %>
                         <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_RESPONSES_BUTTON)) { %>
                         <% if (helpdeskBean.getUserInfoBean().getResponseInfoBean() != null) { %>
                         <button id="helpdesk_clearResponsesBtn" class="btn" style="width:150px">
@@ -585,8 +611,8 @@
                         </pwm:script>
                         <% } %>
                         <% } %>
-                        <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_OTP_BUTTON) &&
-                                pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.OTP_ENABLED)) { %>
+                        <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_CLEAR_OTP_BUTTON) && pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.OTP_ENABLED)) { %>
+                        <% if (hasOtp) { %>
                         <button id="helpdesk_clearOtpSecretBtn" class="btn" style="width:150px">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-eraser"></span></pwm:if>
                             <pwm:display key="Button_HelpdeskClearOtpSecret"/>
@@ -595,53 +621,22 @@
                             <script type="text/javascript">
                                 PWM_GLOBAL['startupFunctions'].push(function(){
                                     PWM_MAIN.addEventHandler('helpdesk_clearOtpSecretBtn','click',function(){
-                                        PWM_MAIN.showConfirmDialog({text:PWM_MAIN.showString('Button_HelpdeskClearOtpSecret'),okAction:function() {
+                                        PWM_MAIN.showConfirmDialog({okAction:function() {
                                             document.clearOtpSecretForm.submit();
                                         }});
                                     });
                                 });
                             </script>
                         </pwm:script>
-                        <br/>
-                        <% } %>
-                        <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE_UNLOCK)) { %>
-                        <% if (helpdeskBean.getAdditionalUserInfo().isIntruderLocked()) { %>
-                        <button class="helpdesk_unlockBtn">
-                            <pwm:if test="showIcons"><span class="btn-icon fa fa-unlock"></span></pwm:if>
-                            <pwm:display key="Button_Unlock"/>
-                        </button>
-                        <pwm:script>
-                            <script type="text/javascript">
-                                PWM_GLOBAL['startupFunctions'].push(function(){
-                                    PWM_MAIN.addEventHandler('helpdesk_unlockBtn','click',function(){
-                                        PWM_MAIN.showConfirmDialog({text:PWM_MAIN.showString('Button_Unlock'),okAction:function() {
-                                            document.ldapUnlockForm.submit();
-                                        }});
-                                    });
-                                });
-                            </script>
-                        </pwm:script>
-                        <br/>
                         <% } else { %>
-                        <button id="helpdesk_unlockBtn" class="btn" disabled="disabled" style="width:150px">
-                            <pwm:if test="showIcons"><span class="btn-icon fa fa-unlock"></span></pwm:if>
-                            <pwm:display key="Button_Unlock"/>
+                        <button id="helpdesk_clearOtpSecretBtn" class="btn" disabled="disabled" style="width:150px">
+                            <pwm:if test="showIcons"><span class="btn-icon fa fa-eraser"></span></pwm:if>
+                            <pwm:display key="Button_HelpdeskClearOtpSecret"/>
                         </button>
-                        <br/>
-                        <pwm:script>
-                            <script type="text/javascript">
-                                PWM_GLOBAL['startupFunctions'].push(function(){
-                                    PWM_MAIN.showTooltip({
-                                        id: "helpdesk_unlockBtn",
-                                        text: 'User is not locked'
-                                    });
-                                });
-                            </script>
-                        </pwm:script>
                         <% } %>
+                        <br/>
                         <% } %>
                         <% if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_ENABLE_OTP_VERIFY)) { %>
-                        <% boolean hasOtp = searchedUserInfo.getOtpUserRecord() != null; %>
                         <button id="helpdesk_verifyOtpButton" <%=hasOtp?"":" disabled=\"true\""%>class="btn" style="width:150px">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-mobile-phone"></span></pwm:if>
                             Verify OTP

+ 0 - 1
pwm/servlet/web/WEB-INF/jsp/login.jsp

@@ -26,7 +26,6 @@
 <%@ taglib uri="pwm" prefix="pwm" %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
-<% final PwmRequest login_pwmRequest = PwmRequest.forRequest(request,response); %>
 <body class="nihilo">
 <div id="wrapper" class="login-wrapper">
     <jsp:include page="fragment/header-body.jsp">

+ 12 - 33
pwm/servlet/web/WEB-INF/jsp/setupotpsecret-existing.jsp

@@ -44,32 +44,14 @@
         </p>
         <%@ include file="fragment/message.jsp" %>
         <br/>
-        <table class="noborder" style="table-layout: fixed; width: 150px">
-            <colgroup>
-                <col style="width:130px;"/>
-                <col style="width:20px;padding-top: 5px"/>
-            </colgroup>
-                
-            <tr>
-                <td>
-                    <input type="text" class="inputfield" style="max-width: 130px; width: 130px" pattern="[0-9].*" id="verifyCodeInput" autofocus maxlength="6" />
-                </td>
-                <td>
-                    <span style="display:none;color:green" id="checkIcon" class="btn-icon fa fa-lg fa-check"></span>
-                    <span style="display:none;color:red" id="crossIcon" class="btn-icon fa fa-lg fa-times"></span>
-                    <span style="display:none" id="workingIcon" class="fa fa-lg fa-spin fa-spinner"></span>
-                </td>
-            </tr>
-            <tr>
-                <td colspan="2">
-                    <button type="submit" name="button-verifyCode" class="btn" id="button-verifyCode">
-                        <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
-                        <pwm:display key="Button_CheckCode"/>
-                    </button>
-                </td>
-            </tr>
-        </table>
+        <%--
+        <br/>
+        --%>
         <div class="buttonbar">
+            <button type="submit" name="button-verifyCodeDialog" class="btn" id="button-verifyCodeDialog">
+                <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
+                <pwm:display key="Button_CheckCode"/>
+            </button>
             <form action="<pwm:url url='SetupOtp'/>" method="post" name="setupOtpSecretForm" style="display: inline"
                   enctype="application/x-www-form-urlencoded" id="setupOtpSecretForm">
                 <input type="hidden" name="processAction" value="clearOtp"/>
@@ -85,14 +67,11 @@
     <div class="push"></div>
 </div>
 <pwm:script>
-<script type="application/javascript">
-    PWM_GLOBAL['startupFunctions'].push(function(){
-        PWM_OTP.initExistingOtpPage();
-        PWM_MAIN.addEventHandler('button-verifyCode','click',function(){
-            PWM_OTP.checkExistingCode();
-        })
-    });
-</script>
+    <script type="application/javascript">
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_OTP.initExistingOtpPage();
+        });
+    </script>
 </pwm:script>
 <pwm:script-ref url="/public/resources/js/otpsecret.js"/>
 <%@ include file="fragment/footer.jsp" %>

+ 14 - 8
pwm/servlet/web/WEB-INF/jsp/shortcut.jsp

@@ -33,6 +33,7 @@
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <%
+    final boolean newWindow = JspUtility.getPwmRequest(pageContext).getConfig().readSettingAsBoolean(PwmSetting.SHORTCUT_NEW_WINDOW);
     Map<String, ShortcutItem> shortcutItems = Collections.emptyMap();
     try {
         final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
@@ -51,16 +52,21 @@
         <% if (shortcutItems.isEmpty()) { %>
         <p>No shortcuts</p>
         <% } else { %>
-        <table style="border:0">
+        <table class="noborder">
             <% for (final ShortcutItem item : shortcutItems.values()) { %>
-            <tr style="border:0">
-                <td style="border:0; text-align: right; width:10%">
-                    <% final boolean newWindow = JspUtility.getPwmRequest(pageContext).getConfig().readSettingAsBoolean(PwmSetting.SHORTCUT_NEW_WINDOW); %>
-                    <a class="menubutton" <%=newWindow?" target=\"" + item.getLabel() + "\" " : ""%> href="<pwm:url url='/private/Shortcuts' addContext="true"/>?processAction=selectShortcut&link=<%= item.getLabel() %>">
-                        <%= item.getLabel() %>
-                    </a>
+            <tr>
+                <td class="menubutton_key">
+                    <form action="<pwm:url url='Shortcuts'/>" method="post" name="form-shortcuts-<%=item%>" enctype="application/x-www-form-urlencoded" id="form-shortcuts-<%=item%>" <%=newWindow ? " target=\"_blank\"" : ""%>>
+                        <input type="hidden" name="processAction" value="selectShortcut">
+                        <input type="hidden" name="link" value="<%=item.getLabel()%>">
+                        <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
+                        <button type="submit" class="menubutton">
+                            <pwm:if test="showIcons"><span class="btn-icon fa fa-external-link"></span></pwm:if>
+                            <%=item.getLabel()%>
+                        </button>
+                    </form>
                 </td>
-                <td style="border: 0">
+                <td>
                     <p><%= item.getDescription() %></p>
                 </td>
             </tr>

+ 23 - 23
pwm/servlet/web/private/index.jsp

@@ -41,30 +41,30 @@
         <jsp:param name="pwm.PageName" value="Title_MainPage"/>
     </jsp:include>
     <div id="centerbody">
-        <table style="border:0">
+        <table class="noborder">
             <pwm:if test="permission" arg1="CHANGE_PASSWORD">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a id="button_ChangePassword" class="menubutton" href="<pwm:url url='ChangePassword'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-key"></span></pwm:if>
                             <pwm:display key="Title_ChangePassword"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_ChangePassword"/></p>
                     </td>
                 </tr>
             </pwm:if>
             <pwm:if test="setupChallengeEnabled">
                 <pwm:if test="permission" arg1="SETUP_RESPONSE">
-                    <tr style="border:0">
+                    <tr>
                         <td class="menubutton_key">
                             <a class="menubutton" href="<pwm:url url='SetupResponses'/>">
                                 <pwm:if test="showIcons"><span class="btn-icon fa fa-list-ol"></span></pwm:if>
                                 <pwm:display key="Title_SetupResponses"/>
                             </a>
                         </td>
-                        <td style="border: 0">
+                        <td>
                             <p><pwm:display key="Long_Title_SetupResponses"/></p>
                         </td>
                     </tr>
@@ -72,14 +72,14 @@
             </pwm:if>
             <pwm:if test="otpEnabled">
                 <pwm:if test="permission" arg1="SETUP_OTP_SECRET">
-                    <tr style="border:0">
+                    <tr>
                         <td class="menubutton_key">
                             <a class="menubutton" href="<pwm:url url='SetupOtp'/>">
                                 <pwm:if test="showIcons"><span class="btn-icon fa fa-qrcode"></span></pwm:if>
                                 <pwm:display key="Title_SetupOtpSecret"/>
                             </a>
                         </td>
-                        <td style="border: 0">
+                        <td>
                             <p><pwm:display key="Long_Title_SetupOtpSecret"/></p>
                         </td>
                     </tr>
@@ -87,110 +87,110 @@
             </pwm:if>
             <pwm:if test="updateProfileEnabled">
                 <pwm:if test="permission" arg1="PROFILE_UPDATE">
-                    <tr style="border:0">
+                    <tr>
                         <td class="menubutton_key">
                             <a class="menubutton" href="<pwm:url url='UpdateProfile'/>">
                                 <pwm:if test="showIcons"><span class="btn-icon fa fa-edit"></span></pwm:if>
                                 <pwm:display key="Title_UpdateProfile"/>
                             </a>
                         </td>
-                        <td style="border: 0">
+                        <td>
                             <p><pwm:display key="Long_Title_UpdateProfile"/></p>
                         </td>
                     </tr>
                 </pwm:if>
             </pwm:if>
             <pwm:if test="shortcutsEnabled">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='Shortcuts'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-external-link"></span></pwm:if>
                             <pwm:display key="Title_Shortcuts"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_Shortcuts"/></p>
                     </td>
                 </tr>
             </pwm:if>
             <pwm:if test="peopleSearchEnabled">
                 <pwm:if test="permission" arg1="PEOPLE_SEARCH">
-                    <tr style="border:0">
+                    <tr>
                         <td class="menubutton_key">
                             <a class="menubutton" href="<pwm:url url='PeopleSearch'/>">
                                 <pwm:if test="showIcons"><span class="btn-icon fa fa-search"></span></pwm:if>
                                 <pwm:display key="Title_PeopleSearch"/>
                             </a>
                         </td>
-                        <td style="border: 0">
+                        <td>
                             <p><pwm:display key="Long_Title_PeopleSearch"/></p>
                         </td>
                     </tr>
                 </pwm:if>
             </pwm:if>
             <pwm:if test="accountInfoEnabled">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='userinfo.jsp'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-file-o"></span></pwm:if>
                             <pwm:display key="Title_UserInformation"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_UserInformation"/></p>
                     </td>
                 </tr>
             </pwm:if>
             <% if (pwmRequest.getPwmSession().getSessionManager().getHelpdeskProfile(pwmRequest.getPwmApplication()) != null) { %>
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='Helpdesk'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-user"></span></pwm:if>
                             <pwm:display key="Title_Helpdesk"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_Helpdesk"/></p>
                     </td>
                 </tr>
             <% } %>
             <% if (ContextManager.getPwmApplication(session).getConfig() != null && ContextManager.getPwmApplication(session).getConfig().readSettingAsBoolean(PwmSetting.GUEST_ENABLE)) { %>
             <pwm:if test="permission" arg1="GUEST_REGISTRATION">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='GuestRegistration'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-group"></span></pwm:if>
                             <pwm:display key="Title_GuestRegistration"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_GuestRegistration"/></p>
                     </td>
                 </tr>
             </pwm:if>
             <% } %>
             <pwm:if test="permission" arg1="PWMADMIN">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='/private/admin/Administration' addContext="true"/> ">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-dashboard"></span></pwm:if>
                             <pwm:display key="Title_Admin"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_Admin"/></p>
                     </td>
                 </tr>
             </pwm:if>
             <pwm:if test="showLogout">
-                <tr style="border:0">
+                <tr>
                     <td class="menubutton_key">
                         <a class="menubutton" href="<pwm:url url='../public/Logout'/>">
                             <pwm:if test="showIcons"><span class="btn-icon fa fa-sign-out"></span></pwm:if>
                             <pwm:display key="Title_Logout"/>
                         </a>
                     </td>
-                    <td style="border: 0">
+                    <td>
                         <p><pwm:display key="Long_Title_Logout"/></p>
                     </td>
                 </tr>

+ 5 - 7
pwm/servlet/web/public/reference/referencedoc.jsp

@@ -40,12 +40,10 @@
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
     PwmRequest pwmRequest = null;
-    final Map<String,PwmSettingCategory> sortedCategories = new TreeMap<String,PwmSettingCategory>();
+    List<PwmSettingCategory> sortedCategories = new ArrayList();
     try {
         pwmRequest = PwmRequest.forRequest(request, response);
-        for (final PwmSettingCategory category : PwmSettingCategory.values()) {
-            sortedCategories.put(category.getLabel(pwmRequest.getLocale()),category);
-        }
+        sortedCategories = PwmSettingCategory.sortedValues(pwmRequest.getLocale());
     } catch (PwmException e) {
         JspUtility.logError(pageContext, "error during page setup: " + e.getMessage());
     }
@@ -65,9 +63,9 @@
             <li><a href="#errors">Errors</a></li>
             <li><a href="#settings">Settings</a></li>
             <ol>
-                <% for (final PwmSettingCategory category : sortedCategories.values()) { %>
+                <% for (final PwmSettingCategory category : sortedCategories) { %>
                 <% if (!category.isHidden() && !category.getSettings().isEmpty()) { %>
-                <li><a href="#settings_category_<%=category.toString()%>"><%=category.getLabel(userLocale)%></a></li>
+                <li><a href="#settings_category_<%=category.toString()%>"><%=category.toMenuLocationDebug(null,userLocale)%></a></li>
                 <% } %>
                 <% } %>
             </ol>
@@ -179,7 +177,7 @@
             <% } %>
         </table>
         <h1><a id="settings">Configuration Settings</a></h1>
-        <% for (final PwmSettingCategory category : sortedCategories.values()) { %>
+        <% for (final PwmSettingCategory category : sortedCategories) { %>
         <% if (!category.isHidden() && !category.getSettings().isEmpty()) { %>
         <h2><a id="settings_category_<%=category.toString()%>"><%=category.getLabel(userLocale)%></a></h2>
         <p>

+ 49 - 9
pwm/servlet/web/public/resources/configStyle.css

@@ -97,8 +97,8 @@
 }
 
 .centerbody-config {
-    width: 800px;
-    min-width: 800px;
+    width: 850px;
+    min-width: 850px;
     padding: 10px;
     margin-left: auto;
     margin-right: auto;
@@ -116,7 +116,7 @@
 }
 
 #header-center-wide {
-    width: 800px;
+    width: 850px;
     margin: 0 auto;
     position:relative;
     padding:4px;
@@ -252,7 +252,7 @@ table {
     background-color: white;
     margin-top: 57px;
     height: 85vh;
-    width: 192px;
+    width: 242px;
     position: fixed;
     animation: fadein 1.5s;
     -moz-animation: fadein 1.5s; /* Firefox */
@@ -284,7 +284,7 @@ table {
     position:fixed;
     z-index: 2;
     background: white;
-    width:800px;
+    width:850px;
     animation: fadein 3s;
     -moz-animation: fadein 3s; /* Firefox */
     -webkit-animation: fadein 3s; /* Safari and Chrome */
@@ -400,10 +400,6 @@ table {
     background-color: none !important;
 } */
 
-@keyframes fadein { from { opacity:0; } to { opacity:1; } }
-@-moz-keyframes fadein { from { opacity:0 } to { opacity:1 } }
-@-webkit-keyframes fadein { from { opacity:0 } to { opacity:1 } }
-
 .panel-searchResultItem {
     text-indent: 1.5em; 
     margin-left: 10px;
@@ -429,6 +425,15 @@ table {
     white-space: nowrap;
 }
 
+.configStringPanel {
+    width:480px;
+    max-width:480px;
+    overflow:hidden;
+    text-overflow: ellipsis;
+    cursor: pointer;
+    max-height:50px;
+}
+
 .editorIdleStatus {
     /*
     margin-left: auto;
@@ -451,3 +456,38 @@ table {
     margin-left: 5px;
 }
 
+input[type=range] {
+    padding:0;
+}
+input[type=range]::-ms-tooltip {
+    display: none;
+}
+input[type=range]::-ms-track {
+    width: 300px;
+    height: 5px;
+    background: transparent;
+    border-color: transparent;
+    border-width: 6px 0;
+    color: transparent;
+}
+input[type=range]::-ms-fill-lower {
+    background: #777;
+    border-radius: 10px;
+}
+input[type=range]::-ms-fill-upper {
+    background: #ddd;
+    border-radius: 10px;
+}
+input[type=range]::-ms-thumb {
+    border: none;
+    height: 16px;
+    width: 16px;
+    border-radius: 50%;
+    background: #656565;
+}
+input[type=range]:focus::-ms-fill-lower {
+    background: #888;
+}
+input[type=range]:focus::-ms-fill-upper {
+    background: #ccc;
+}

+ 8 - 9
pwm/servlet/web/public/resources/js/admin.js

@@ -612,14 +612,9 @@ PWM_ADMIN.showAppHealth = function(parentDivID, options, refreshNow) {
 
         if (refreshNow) {
             parentDiv.innerHTML = '<div class="WaitDialogBlank" style="margin-top: 20px; margin-bottom: 20px"/>';
-            refreshUrl += refreshUrl.indexOf('?') > 0 ? '&' : '?';
-            refreshUrl += "&refreshImmediate=true";
+            refreshUrl = PWM_MAIN.addParamToUrl(refreshUrl, 'refreshImmediate', 'true');
         }
 
-        PWM_GLOBAL['healthRefreshFunction'] = function() {
-            PWM_ADMIN.showAppHealth(parentDivID, options, refreshNow);
-        };
-
         var loadFunction = function(data) {
             if (data['error']) {
                 PWM_MAIN.showErrorDialog(data);
@@ -627,7 +622,13 @@ PWM_ADMIN.showAppHealth = function(parentDivID, options, refreshNow) {
                 PWM_GLOBAL['pwm-health'] = data['data']['overall'];
                 var htmlBody = PWM_ADMIN.makeHealthHtml(data['data'], showTimestamp, showRefresh);
                 parentDiv.innerHTML = htmlBody;
+
+                PWM_MAIN.addEventHandler('button-refreshHealth','click',function(){
+                    PWM_ADMIN.showAppHealth(parentDivID, options, true);
+                });
+
                 PWM_GLOBAL['healthCheckInProgress'] = false;
+
                 if (refreshTime > 0) {
                     setTimeout(function() {
                         PWM_ADMIN.showAppHealth(parentDivID, options);
@@ -693,9 +694,7 @@ PWM_ADMIN.makeHealthHtml = function(healthData, showTimestamp, showRefresh) {
             htmlBody += '</span>&nbsp;&nbsp;&nbsp;&nbsp;';
         }
         if (showRefresh) {
-            htmlBody += '<a title="refresh" href="#" onclick="PWM_GLOBAL[\'healthRefreshFunction\']()">';
-            htmlBody += '<span class="fa fa-refresh"></span>';
-            htmlBody += '</a>';
+            htmlBody += '<span id="button-refreshHealth" class="fa btn-icon fa-refresh"></span>';
         }
         htmlBody += "</td></tr>";
     }

+ 353 - 172
pwm/servlet/web/public/resources/js/configeditor-settings.js

@@ -33,136 +33,143 @@ PWM_VAR['clientSettingCache'] = { };
 // -------------------------- locale table handler ------------------------------------
 var LocalizedStringValueHandler = {};
 
-LocalizedStringValueHandler.init = function(keyName, regExPattern, syntax) {
-    console.log('LocalizedStringValueHandler init for ' + keyName);
+PWM_VAR['LocalizedStringValueHandler-settingData'] = {};
+LocalizedStringValueHandler.init = function(settingKey, settingData) {
+    console.log('LocalizedStringValueHandler init for ' + settingKey);
 
-    var parentDiv = 'table_setting_' + keyName;
-    PWM_MAIN.getObject(parentDiv).innerHTML = '<table id="tableTop_' + keyName + '" style="border-width:0">';
-    parentDiv = PWM_MAIN.getObject('tableTop_' + keyName);
+    if (settingData) {
+        PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey] = settingData;
+    } else {
+        PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey] = PWM_SETTINGS['settings'][settingKey];
+    }
+
+    var parentDiv = 'table_setting_' + settingKey;
+    PWM_MAIN.getObject(parentDiv).innerHTML = '<table id="tableTop_' + settingKey + '" style="border-width:0">';
+    parentDiv = PWM_MAIN.getObject('tableTop_' + settingKey);
 
-    PWM_VAR['clientSettingCache'][keyName + "_regExPattern"] = regExPattern;
-    PWM_VAR['clientSettingCache'][keyName + "_syntax"] = syntax;
-    PWM_VAR['clientSettingCache'][keyName + "_parentDiv"] = parentDiv;
+    PWM_VAR['clientSettingCache'][settingKey + "_parentDiv"] = parentDiv;
     PWM_CFGEDIT.clearDivElements(parentDiv, true);
-    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
-        PWM_VAR['clientSettingCache'][keyName] = resultValue;
-        LocalizedStringValueHandler.draw(keyName);
+    PWM_CFGEDIT.readSetting(settingKey, function(resultValue) {
+        PWM_VAR['clientSettingCache'][settingKey] = resultValue;
+        LocalizedStringValueHandler.draw(settingKey);
     });
 };
 
-LocalizedStringValueHandler.draw = function(keyName) {
-    var parentDiv = PWM_VAR['clientSettingCache'][keyName + "_parentDiv"];
-    var regExPattern = PWM_VAR['clientSettingCache'][keyName + "_regExPattern"];
-    var syntax = PWM_VAR['clientSettingCache'][keyName + "_syntax"];
+LocalizedStringValueHandler.draw = function(settingKey) {
+    var parentDiv = PWM_VAR['clientSettingCache'][settingKey + "_parentDiv"];
+    var settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey];
 
-    require(["dojo/parser","dijit/form/Button","dijit/form/Textarea","dijit/form/ValidationTextBox"],function(dojoParser){
-        var resultValue = PWM_VAR['clientSettingCache'][keyName];
-        PWM_CFGEDIT.clearDivElements(parentDiv, false);
-        for (var i in resultValue) {
-            LocalizedStringValueHandler.addLocaleTableRow(parentDiv, keyName, i, resultValue[i], regExPattern, syntax)
+    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
+    PWM_CFGEDIT.clearDivElements(parentDiv, false);
+    if (PWM_MAIN.isEmpty(resultValue)) {
+        parentDiv.innerHTML = '<button class="btn" id="button-' + settingKey + '-addValue"><span class="btn-icon fa fa-plus-square"></span>Add Value</button>';
+        PWM_MAIN.addEventHandler('button-' + settingKey + '-addValue','click',function(){
+            UILibrary.stringEditorDialog({
+                title:'Add Value',
+                textarea:('LOCALIZED_TEXT_AREA' == settingData['syntax']),
+                regex:'pattern' in settingData ? settingData['pattern'] : '.+',
+                placeholder:settingData['placeholder'],
+                value:'',
+                completeFunction:function(value){
+                    LocalizedStringValueHandler.writeLocaleSetting(settingKey,'',value);
+                }
+            });
+        })
+    } else {
+        for (var localeKey in resultValue) {
+            LocalizedStringValueHandler.drawRow(parentDiv, settingKey, localeKey, resultValue[localeKey])
         }
-        PWM_CFGEDIT.addAddLocaleButtonRow(parentDiv, keyName, function(localeKey) {
-            LocalizedStringValueHandler.addLocaleSetting(keyName, localeKey);
+        PWM_CFGEDIT.addAddLocaleButtonRow(parentDiv, settingKey, function(localeKey) {
+            LocalizedStringValueHandler.addLocaleSetting(settingKey, localeKey);
         });
+    }
 
-        PWM_VAR['clientSettingCache'][keyName] = resultValue;
-        dojoParser.parse(parentDiv);
-    });
+    PWM_VAR['clientSettingCache'][settingKey] = resultValue;
 };
 
-LocalizedStringValueHandler.addLocaleTableRow = function(parentDiv, settingKey, localeString, value, regExPattern, syntax) {
+LocalizedStringValueHandler.drawRow = function(parentDiv, settingKey, localeString, value) {
+    var settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey];
     var inputID = 'value-' + settingKey + '-' + localeString;
 
-    // clear the old dijit node (if it exists)
-    PWM_MAIN.clearDijitWidget(inputID);
-
     var newTableRow = document.createElement("tr");
     newTableRow.setAttribute("style", "border-width: 0");
 
+    var tableHtml = '<td style="border-width:0; width: 15px">';
+    if (localeString != null && localeString.length > 0) {
+        tableHtml += localeString;
+    }
+    tableHtml += '</td>';
 
-    var td1 = document.createElement("td");
-    td1.setAttribute("style", "border-width: 0; width: 15px");
+    tableHtml += '<td id="button-' + inputID + '" style="border-width:0; width: 15px"><span class="fa fa-edit"/></ta>';
 
-    if (localeString == null || localeString.length < 1) {
-        td1.innerHTML = "";
-    } else {
-        td1.innerHTML = localeString;
-    }
-    newTableRow.appendChild(td1);
-
-
-    var td2 = document.createElement("td");
-    td2.setAttribute("style", "border-width: 0");
-    if (syntax == 'LOCALIZED_TEXT_AREA') {
-        var textAreaElement = document.createElement("textarea");
-        textAreaElement.setAttribute("id", inputID);
-        textAreaElement.setAttribute("value", PWM_MAIN.showString('Display_PleaseWait'));
-        textAreaElement.setAttribute("onchange", "LocalizedStringValueHandler.writeLocaleSetting('" + settingKey + "','" + localeString + "',this.value)");
-        textAreaElement.setAttribute("style", "width: 510px; max-width:510px; max-height: 300px; overflow: auto; white-space: nowrap");
-        textAreaElement.setAttribute("data-dojo-type", "dijit.form.Textarea");
-        textAreaElement.setAttribute("value", value);
-        td2.appendChild(textAreaElement);
-    } else {
-        var inputElement = document.createElement("input");
-        inputElement.setAttribute("id", inputID);
-        inputElement.setAttribute("value", PWM_MAIN.showString('Display_PleaseWait'));
-        inputElement.setAttribute("onchange", "LocalizedStringValueHandler.writeLocaleSetting('" + settingKey + "','" + localeString + "',this.value)");
-        inputElement.setAttribute("style", "width: 510px;");
-        inputElement.setAttribute("data-dojo-type", "dijit.form.ValidationTextBox");
-        inputElement.setAttribute("regExp", regExPattern);
-        inputElement.setAttribute("value", value);
-        td2.appendChild(inputElement);
-    }
-    newTableRow.appendChild(td2);
+    tableHtml += '<td id="panel-' + inputID + '">';
+    tableHtml += '<div class="configStringPanel">' + ((value != null && value.length > 0) ? value : '&nbsp;') + '</div>';
+    tableHtml += '</td>';
 
-    if (localeString != null && localeString.length > 0) {
-        var imgElement = document.createElement("div");
-        imgElement.setAttribute("style", "width: 10px; height: 10px;");
-        imgElement.setAttribute("class", "delete-row-icon action-icon fa fa-times");
-        imgElement.setAttribute("id", "button-" + settingKey + '-' + localeString + "-deleteRow");
-        imgElement.setAttribute("onclick", "LocalizedStringValueHandler.removeLocaleSetting('" + settingKey + "','" + localeString + "','" + parentDiv + "','" + regExPattern + "','" + syntax + "')");
-        td2.appendChild(imgElement);
+    var defaultLocale = (localeString == null || localeString.length < 1);
+    var required = settingData['required'];
+    var hasNonDefaultValues = PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1 ;
+
+    if (!defaultLocale || !required && !hasNonDefaultValues) {
+        tableHtml += '<div style="width: 10px; height: 10px;" class="delete-row-icon action-icon fa fa-times"'
+        + 'id="button-' + settingKey + '-' + localeString + '-deleteRow"></div>';
     }
 
+    newTableRow.innerHTML = tableHtml;
     var parentDivElement = PWM_MAIN.getObject(parentDiv);
     parentDivElement.appendChild(newTableRow);
 
     PWM_MAIN.addEventHandler("button-" + settingKey + '-' + localeString + "-deleteRow","click",function(){
         LocalizedStringValueHandler.removeLocaleSetting(settingKey, localeString);
     });
+
+    var editFunction = function() {
+        UILibrary.stringEditorDialog({
+            title:'Edit Value',
+            textarea:('LOCALIZED_TEXT_AREA' == settingData['syntax']),
+            regex:'pattern' in settingData ? settingData['pattern'] : '.+',
+            placeholder:settingData['placeholder'],
+            value:value,
+            completeFunction:function(value){
+                LocalizedStringValueHandler.writeLocaleSetting(settingKey,localeString,value);
+            }
+        });
+    };
+
+    PWM_MAIN.addEventHandler("panel-" + inputID,'click',function(){ editFunction(); });
+    PWM_MAIN.addEventHandler("button-" + inputID,'click',function(){ editFunction(); });
 };
 
 LocalizedStringValueHandler.writeLocaleSetting = function(settingKey, locale, value) {
     var existingValues = PWM_VAR['clientSettingCache'][settingKey];
-    var currentValues = { };
-    for (var i in existingValues) {
-        var inputID = 'value-' + settingKey + '-' + i;
-        currentValues[i] = PWM_MAIN.getObject(inputID).value;
-    }
-    if (value == null) {
-        delete currentValues[locale];
-    } else {
-        currentValues[locale] = value;
-    }
-    PWM_CFGEDIT.writeSetting(settingKey, currentValues);
-    PWM_VAR['clientSettingCache'][settingKey] = currentValues;
+    existingValues[locale] = value;
+    PWM_CFGEDIT.writeSetting(settingKey, existingValues);
+    LocalizedStringValueHandler.draw(settingKey);
 };
 
-LocalizedStringValueHandler.removeLocaleSetting = function(keyName, locale, parentDiv, regExPattern, syntax) {
-    LocalizedStringValueHandler.writeLocaleSetting(keyName, locale, null);
-    LocalizedStringValueHandler.draw(keyName);
+LocalizedStringValueHandler.removeLocaleSetting = function(settingKey, locale) {
+    var existingValues = PWM_VAR['clientSettingCache'][settingKey];
+    delete existingValues[locale];
+    PWM_CFGEDIT.writeSetting(settingKey, existingValues);
+    LocalizedStringValueHandler.draw(settingKey);
 };
 
-LocalizedStringValueHandler.addLocaleSetting = function(keyName, inputValue) {
-    try {
-        var existingElementForLocale = PWM_MAIN.getObject('value-' + keyName + '-' + inputValue);
-        if (existingElementForLocale == null) {
-            PWM_VAR['clientSettingCache'][keyName][inputValue] = [];
-            PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName]);
-            //LocalizedStringValueHandler.writeLocaleSetting(keyName, inputValue, '');
-            LocalizedStringValueHandler.draw(keyName);
-        }
-    } finally {
+LocalizedStringValueHandler.addLocaleSetting = function(settingKey, localeKey) {
+    var existingValues = PWM_VAR['clientSettingCache'][settingKey];
+    var settingData = PWM_VAR['LocalizedStringValueHandler-settingData'][settingKey];
+    if (localeKey in existingValues) {
+        PWM_MAIN.showErrorDialog('Locale ' + localeKey + ' is already present.');
+    } else {
+        UILibrary.stringEditorDialog({
+            title:'Add Value - ' + localeKey,
+            textarea:('LOCALIZED_TEXT_AREA' == settingData['syntax']),
+            regex:'pattern' in settingData ? settingData['pattern'] : '.+',
+            placeholder:settingData['placeholder'],
+            value:'',
+            completeFunction:function(value){
+                LocalizedStringValueHandler.writeLocaleSetting(settingKey,localeKey,value);
+            }
+        });
     }
 };
 
@@ -177,7 +184,7 @@ StringArrayValueHandler.init = function(keyName) {
     console.log('StringArrayValueHandler init for ' + keyName);
 
     var parentDiv = 'table_setting_' + keyName;
-    PWM_MAIN.getObject(parentDiv).innerHTML = '<table id="tableTop_' + keyName + '" style="border-width:0">';
+    PWM_MAIN.getObject(parentDiv).innerHTML = '<div id="tableTop_' + keyName + '">';
     parentDiv = PWM_MAIN.getObject('tableTop_' + keyName);
 
     PWM_VAR['clientSettingCache'][keyName + "_options"] = PWM_VAR['clientSettingCache'][keyName + "_options"] || {};
@@ -219,13 +226,14 @@ StringArrayValueHandler.draw = function(settingKey) {
 
     var counter = 0;
     var itemCount = PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]);
+    parentDivElement.appendChild(tableElement);
+
     for (var i in resultValue) {
         (function(iteration) {
             StringArrayValueHandler.drawRow(settingKey, iteration, resultValue[iteration], itemCount, tableElement);
             counter++;
         })(i);
     }
-    parentDivElement.appendChild(tableElement);
 
     var addItemButton = document.createElement("button");
     addItemButton.setAttribute("type", "button");
@@ -234,11 +242,8 @@ StringArrayValueHandler.draw = function(settingKey) {
     addItemButton.innerHTML = '<span class="btn-icon fa fa-plus-square"></span>' + (syntax == 'PROFILE' ? "Add Profile" : "Add Value");
     parentDivElement.appendChild(addItemButton);
 
-    require(["dojo/parser","dijit/form/Button","dijit/form/ValidationTextBox"],function(dojoParser){
-        dojoParser.parse(parentDiv);
-        PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem','click',function(){
-            StringArrayValueHandler.valueHandler(settingKey,-1);
-        });
+    PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem','click',function(){
+        StringArrayValueHandler.valueHandler(settingKey,-1);
     });
 };
 
@@ -255,7 +260,7 @@ StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCou
     valueRow.setAttribute("style", "border-width: 0");
     valueRow.setAttribute("id",inputID + "_row");
 
-    var rowHtml = '<td style=""><div style="width:500px; overflow:hidden; text-overflow: ellipsis" id="' + inputID + '">' + value + '</div></td>';
+    var rowHtml = '<td style=""><div class="configStringPanel" id="' + inputID + '">' + value + '</div></td>';
 
     var downButtonID = 'button-' + settingKey + '-' + iteration + '-moveDown';
     rowHtml += '<td style="border:0">';
@@ -282,56 +287,27 @@ StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCou
     valueRow.innerHTML = rowHtml;
     parentDivElement.appendChild(valueRow);
 
-    setTimeout(function(){
-        if (syntax != 'PROFILE') {
-            PWM_MAIN.addEventHandler(inputID,'click',function(){
-                StringArrayValueHandler.valueHandler(settingKey,iteration);
-            });
-        }
+    if (syntax != 'PROFILE') {
+        PWM_MAIN.addEventHandler(inputID,'click',function(){
+            StringArrayValueHandler.valueHandler(settingKey,iteration);
+        });
+    }
 
-        if (itemCount > 1 && iteration != (itemCount -1)) {
-            PWM_MAIN.addEventHandler(downButtonID,'click',function(){StringArrayValueHandler.move(settingKey,false,iteration)});
-        }
+    if (itemCount > 1 && iteration != (itemCount -1)) {
+        PWM_MAIN.addEventHandler(downButtonID,'click',function(){StringArrayValueHandler.move(settingKey,false,iteration)});
+    }
 
-        if (itemCount > 1 && iteration != 0) {
-            PWM_MAIN.addEventHandler(upButtonID,'click',function(){StringArrayValueHandler.move(settingKey,true,iteration)});
-        }
+    if (itemCount > 1 && iteration != 0) {
+        PWM_MAIN.addEventHandler(upButtonID,'click',function(){StringArrayValueHandler.move(settingKey,true,iteration)});
+    }
 
-        if (itemCount > 1 || !PWM_SETTINGS['settings'][settingKey]['required']) {
-            PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){StringArrayValueHandler.removeValue(settingKey,iteration)});
-        }
-    },100);
+    if (itemCount > 1 || !PWM_SETTINGS['settings'][settingKey]['required']) {
+        PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){StringArrayValueHandler.removeValue(settingKey,iteration)});
+    }
 };
 
 StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
-    var text = '';
-    //text += '<div>' + PWM_SETTINGS['settings'][settingKey]['description'] + '</div><hr/>';
-    text += '<input style="width: 500px" required="required" id="addValueDialog_input"/>';
-
-    var changeFunction = function() {
-        PWM_VAR['addDialog_value'] = this.value;
-        PWM_MAIN.getObject('dialog_ok_button').disabled = !this.validate();
-    };
-
-    var loadFunction = function() {
-        var value = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
-        PWM_MAIN.getObject('dialog_ok_button').disabled = true;
-        require(["dijit/form/ValidationTextBox"],function(ValidationTextBox) {
-            new ValidationTextBox({
-                id:"addValueDialog_input",
-                regExp: PWM_SETTINGS['settings'][settingKey]['pattern'],
-                style: 'width: 500px',
-                required: true,
-                invalidMessage: 'The value does not have the correct format',
-                value: value,
-                onChange: changeFunction,
-                onKeyUp: changeFunction
-            },"addValueDialog_input");
-        });
-    };
-
-    var okAction = function() {
-        var value = PWM_VAR['addDialog_value'];
+    var okAction = function(value) {
         if (iteration > -1) {
             PWM_VAR['clientSettingCache'][settingKey][iteration] = value;
         } else {
@@ -340,15 +316,13 @@ StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
         StringArrayValueHandler.writeSetting(settingKey)
     };
 
-    PWM_MAIN.showDialog({
-        title:PWM_SETTINGS['settings'][settingKey]['label'] + " - " + (iteration > -1 ? "Edit" : "Add") + " Value",
-        text:text,
-        loadFunction:loadFunction,
-        okAction:okAction,
-        showCancel:true,
-        showClose: true,
-        allowMove: true
-    });
+    var editorOptions = {};
+    editorOptions['title'] = PWM_SETTINGS['settings'][settingKey]['label'] + " - " + (iteration > -1 ? "Edit" : "Add") + " Value";
+    editorOptions['regex'] = PWM_SETTINGS['settings'][settingKey]['pattern'];
+    editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
+    editorOptions['completeFunction'] = okAction;
+    editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
+    UILibrary.stringEditorDialog(editorOptions);
 };
 
 StringArrayValueHandler.move = function(settingKey, moveUp, iteration) {
@@ -1161,22 +1135,22 @@ ChangePasswordHandler.popup = function(settingKey,settingName,writeFunction) {
 };
 
 ChangePasswordHandler.validatePasswordPopupFields = function() {
-        var password1 = PWM_MAIN.getObject('password1').value;
-        var password2 = PWM_MAIN.getObject('password2').value;
+    var password1 = PWM_MAIN.getObject('password1').value;
+    var password2 = PWM_MAIN.getObject('password2').value;
 
-        var matchStatus = "";
+    var matchStatus = "";
 
-        PWM_MAIN.getObject('button-storePassword').disabled = true;
-        if (password2.length > 0) {
-            if (password1 == password2) {
-                matchStatus = "MATCH";
-                PWM_MAIN.getObject('button-storePassword').disabled = false;
-            } else {
-                matchStatus = "NO_MATCH";
-            }
+    PWM_MAIN.getObject('button-storePassword').disabled = true;
+    if (password2.length > 0) {
+        if (password1 == password2) {
+            matchStatus = "MATCH";
+            PWM_MAIN.getObject('button-storePassword').disabled = false;
+        } else {
+            matchStatus = "NO_MATCH";
         }
+    }
 
-        ChangePasswordHandler.markConfirmationCheck(matchStatus);
+    ChangePasswordHandler.markConfirmationCheck(matchStatus);
 };
 
 ChangePasswordHandler.markConfirmationCheck = function(matchStatus) {
@@ -1338,6 +1312,7 @@ ActionHandler.defaultValue = {
     ldapMethod:"replace",
     url:"",
     body:"",
+    bodyEncoding:"url",
     headers:{},
     attributeName:"",
     attributeValue:""
@@ -1353,6 +1328,10 @@ ActionHandler.ldapMethodOptions = [
     { label: "Add", value: "add" },
     { label: "Remove", value: "remove" }
 ];
+ActionHandler.bodyEncodingOptions = [
+    { label: "None", value: "none" },
+    { label: "URL Encoding", value: "url" }
+];
 
 ActionHandler.init = function(keyName) {
     console.log('ActionHandler init for ' + keyName);
@@ -1515,7 +1494,7 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
         if (PWM_VAR['clientSettingCache'][keyName][iteration]['type'] == 'webservice') {
             titleText = 'Web Service options for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name'];
             bodyText += '<tr>';
-            bodyText += '<td class="key">HTTP Method</td><td style="border:0;"><select id="select-' + inputID + '-method' + '">';
+            bodyText += '<td class="key">HTTP Method</td><td style="border:0;"><select id="select-' + inputID + '-method">';
 
             for (var optionItem in ActionHandler.httpMethodOptions) {
                 var label = ActionHandler.httpMethodOptions[optionItem]['label'];
@@ -1527,10 +1506,22 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
             bodyText += '</tr><tr>';
             bodyText += '<td class="key">HTTP Headers</td><td><button id="button-' + inputID + '-headers"><span class="btn-icon fa fa-list-ul"/> Edit</button></td>';
             bodyText += '</tr><tr>';
-            bodyText += '<td class="key">URL</td><td><input type="text" placeholder="http://www.example.com/service"  id="input-' + inputID + '-url' + '" value="' + value['url'] + '"/></td>';
-            bodyText += '</tr><tr>';
-            bodyText += '<td class="key">Body</td><td><textarea style="max-width:300px; height:100px; max-height:100px" class="configStringInput" id="input-' + inputID + '-body' + '"/>' + value['body'] + '</textarea></td>';
+            bodyText += '<td class="key">URL</td><td><input type="text" class="configstringinput" style="width:400px" placeholder="http://www.example.com/service"  id="input-' + inputID + '-url' + '" value="' + value['url'] + '"/></td>';
             bodyText += '</tr>';
+            if (PWM_VAR['clientSettingCache'][keyName][iteration]['method'] != 'get') {
+                bodyText += '<tr><td class="key">Body</td><td><textarea style="max-width:400px; height:100px; max-height:100px" class="configStringInput" id="input-' + inputID + '-body' + '"/>' + value['body'] + '</textarea></td></tr>';
+                bodyText += '<tr><td class="key">Body Macro Encoding</td><td>';
+                bodyText += '<select id="select-' + inputID + '-bodyEncoding">';
+                for (var optionItem in ActionHandler.bodyEncodingOptions) {
+                    var label = ActionHandler.bodyEncodingOptions[optionItem]['label'];
+                    var optionValue = ActionHandler.bodyEncodingOptions[optionItem]['value'];
+                    var selected = optionValue == PWM_VAR['clientSettingCache'][keyName][iteration]['bodyEncoding'];
+                    bodyText += '<option value="' + optionValue + '"' + (selected ? ' selected' : '') + '>' + label + '</option>';
+                }
+                bodyText += '</select>';
+                bodyText += '</td></tr>';
+            }
+            bodyText += '';
         } else if (PWM_VAR['clientSettingCache'][keyName][iteration]['type'] == 'ldap') {
             titleText = 'LDAP options for ' + PWM_VAR['clientSettingCache'][keyName][iteration]['name'];
             bodyText += '<tr>';
@@ -1561,7 +1552,15 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
                 });
                 if (PWM_VAR['clientSettingCache'][keyName][iteration]['type'] == 'webservice') {
                     PWM_MAIN.addEventHandler('select-' + inputID + '-method','input',function(){
-                        PWM_VAR['clientSettingCache'][keyName][iteration]['method'] = PWM_MAIN.getObject('select-' + inputID + '-method').value;
+                        var value = PWM_MAIN.getObject('select-' + inputID + '-method').value;
+                        if (value == 'get') {
+                            PWM_VAR['clientSettingCache'][keyName][iteration]['body'] = '';
+                        }
+                        PWM_VAR['clientSettingCache'][keyName][iteration]['method'] = value;
+                        ActionHandler.writeFormSetting(keyName, function(){ ActionHandler.showOptionsDialog(keyName,iteration)});
+                    });
+                    PWM_MAIN.addEventHandler('select-' + inputID + '-bodyEncoding','input',function(){
+                        PWM_VAR['clientSettingCache'][keyName][iteration]['bodyEncoding'] = PWM_MAIN.getObject('select-' + inputID + '-bodyEncoding').value;
                         ActionHandler.writeFormSetting(keyName);
                     });
                     PWM_MAIN.addEventHandler('input-' + inputID + '-url','input',function(){
@@ -2609,6 +2608,119 @@ X509CertificateHandler.draw = function(keyName) {
 };
 
 
+// -------------------------- verification method handler ------------------------------------
+
+var VerificationMethodHandler = {};
+VerificationMethodHandler.init = function(settingKey) {
+    PWM_CFGEDIT.readSetting(settingKey, function(resultValue) {
+        PWM_VAR['clientSettingCache'][settingKey] = resultValue;
+        VerificationMethodHandler.draw(settingKey);
+    });
+};
+
+VerificationMethodHandler.draw = function(settingKey) {
+    var parentDiv = 'table_setting_' + settingKey;
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+
+    var htmlBody = '<table class="">';
+    for (var method in PWM_SETTINGS['verificationMethods']) {
+        var id = settingKey + '-' + method;
+        var label = PWM_SETTINGS['verificationMethods'][method];
+        htmlBody += '<tr><td>' + label + '</td><td><input id="input-range-' + id + '" type="range" min="0" max="2" value="0"/></td>';
+        htmlBody += '<td><span id="label-' + id +'"></span></td></tr>';
+    }
+    htmlBody += '</table>';
+    htmlBody += '<br/><label>Minimum Optional Required <input style="width:30px;" id="input-minOptional-' + settingKey + '" type="number" value="0" class="configNumericInput""></label>';
+    parentDivElement.innerHTML = htmlBody;
+    for (var method in PWM_SETTINGS['verificationMethods']) {
+        var id = settingKey + '-' + method;
+        PWM_MAIN.addEventHandler('input-range-' + id,'change',function(){
+            VerificationMethodHandler.updateLabels(settingKey);
+            VerificationMethodHandler.write(settingKey);
+        });
+        var enabledState = PWM_VAR['clientSettingCache'][settingKey]['methodSettings'][method]['enabledState'];
+        var numberValue = 0;
+        switch (enabledState) {
+            case 'disabled':
+                numberValue = 0;
+                break;
+            case 'optional':
+                numberValue = 1;
+                break;
+            case 'required':
+                numberValue = 2;
+                break;
+            default:
+                alert('unknown value = VerificationMethodHandler.draw');
+        }
+        PWM_MAIN.getObject('input-range-' + id).value = numberValue;
+    }
+    PWM_MAIN.getObject('input-minOptional-' + settingKey).value = PWM_VAR['clientSettingCache'][settingKey]['minOptionalRequired'];
+    PWM_MAIN.addEventHandler('input-minOptional-' + settingKey,'input',function(){
+        VerificationMethodHandler.updateLabels(settingKey);
+        VerificationMethodHandler.write(settingKey);
+    });
+
+    VerificationMethodHandler.updateLabels(settingKey);
+};
+
+VerificationMethodHandler.write = function(settingKey) {
+    var values = {};
+    values['minOptionalRequired'] = Number(PWM_MAIN.getObject('input-minOptional-' + settingKey).value);
+    values['methodSettings'] = {};
+    for (var method in PWM_SETTINGS['verificationMethods']) {
+        var id = settingKey + '-' + method;
+        var value = Number(PWM_MAIN.getObject('input-range-' + id).value);
+
+        var enabledState = 'disabled';
+        switch (value) {
+            case 0:
+                enabledState = 'disabled';
+                break;
+            case 1:
+                enabledState = 'optional';
+                break;
+            case 2:
+                enabledState = 'required';
+                break;
+        }
+        values['methodSettings'][method] = {};
+        values['methodSettings'][method]['enabledState'] = enabledState;
+    }
+    PWM_CFGEDIT.writeSetting(settingKey, values);
+};
+
+VerificationMethodHandler.updateLabels = function(settingKey) {
+    var optionalCount = 0;
+    for (var method in PWM_SETTINGS['verificationMethods']) {
+        var id = settingKey + '-' + method;
+        var value = Number(PWM_MAIN.getObject('input-range-' + id).value);
+        var label = '';
+        switch (value) {
+            case 0:
+                label = 'Not Used';
+                break;
+            case 1:
+                label = 'Optional';
+                optionalCount++;
+                break;
+            case 2:
+                label = 'Required';
+                break;
+            default:
+                alert('unknown value = VerificationMethodHandler.updateLabels');
+        }
+        PWM_MAIN.getObject('label-' + id).innerHTML = label;
+    }
+    var minOptionalInput = PWM_MAIN.getObject('input-minOptional-' + settingKey);
+    minOptionalInput.max = optionalCount;
+    var currentMax = Number(minOptionalInput.value);
+    if (currentMax > optionalCount) {
+        minOptionalInput.value = optionalCount.toString();
+    }
+};
+
+
 // -------------------------- file setting handler ------------------------------------
 
 var FileValueHandler = {};
@@ -2680,3 +2792,72 @@ FileValueHandler.uploadFile = function(keyName) {
     };
     PWM_CONFIG.uploadFile(options);
 };
+
+// -------------------------- common elements handler ------------------------------------
+
+
+var UILibrary = {};
+UILibrary.stringEditorDialog = function(options){
+    options = options === undefined ? {} : options;
+    var title = 'title' in options ? options['title'] : 'Edit Value';
+    var completeFunction = 'completeFunction' in options ? options['completeFunction'] : function() {alert('no string editor dialog complete function')};
+    var regexString = 'regex' in options && options['regex'] ? options['regex'] : '.+';
+    var initialValue = 'value' in options ? options['value'] : '';
+    var placeholder = 'placeholder' in options ? options['placeholder'] : '';
+    var textarea = 'textarea' in options ? options['textarea'] : false;
+
+    var regexObject = new RegExp(regexString);
+    var text = '';
+    text += '<div style="visibility: hidden;" id="panel-valueWarning"><span class="fa fa-warning message-error"></span>&nbsp;' + PWM_CONFIG.showString('Warning_ValueIncorrectFormat') + '</div>';
+    text += '<br/>';
+
+    if (textarea) {
+        text += '<textarea style="max-width: 480px; width: 480px; height:300px; max-height:300px; overflow-y: auto" class="configStringInput" autofocus required id="addValueDialog_input"></textarea>';
+    } else {
+        text += '<input style="width: 480px" class="configStringInput" autofocus required id="addValueDialog_input"/>';
+    }
+
+    var inputFunction = function() {
+        PWM_MAIN.getObject('dialog_ok_button').disabled = true;
+        PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'hidden';
+
+        var value = PWM_MAIN.getObject('addValueDialog_input').value;
+        if (value.length > 0) {
+            var passedValidation = regexObject  != null && regexObject.test(value);
+            if (passedValidation) {
+                PWM_MAIN.getObject('dialog_ok_button').disabled = false;
+                PWM_VAR['temp-dialogInputValue'] = PWM_MAIN.getObject('addValueDialog_input').value;
+            } else {
+                PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'visible';
+            }
+        }
+    };
+
+    var okFunction = function() {
+        var value = PWM_VAR['temp-dialogInputValue'];
+        completeFunction(value);
+    };
+
+    PWM_MAIN.showDialog({
+        title:title,
+        text:text,
+        okAction:okFunction,
+        showCancel:true,
+        showClose: true,
+        allowMove: true,
+        loadFunction:function(){
+            PWM_MAIN.getObject('addValueDialog_input').value = initialValue;
+            if (regexString && regexString.length > 1) {
+                PWM_MAIN.getObject('addValueDialog_input').setAttribute('pattern',regexString);
+            }
+            if (placeholder && placeholder.length > 1) {
+                PWM_MAIN.getObject('addValueDialog_input').setAttribute('placeholder',placeholder);
+            }
+            inputFunction();
+            PWM_MAIN.addEventHandler('addValueDialog_input','input',function(){
+                inputFunction();
+            });
+        }
+    });
+
+};

+ 11 - 4
pwm/servlet/web/public/resources/js/configeditor.js

@@ -773,7 +773,7 @@ PWM_CFGEDIT.databaseHealthCheck = function() {
 
 PWM_CFGEDIT.smsHealthCheck = function() {
     require(["dojo/dom-form"], function(domForm){
-        var dialogBody = '<form id="smsCheckParametersForm"><table>';
+        var dialogBody = '<p>' + PWM_CONFIG.showString('Warning_SmsTestData') + '</p><form id="smsCheckParametersForm"><table>';
         dialogBody += '<tr><td>To</td><td><input name="to" type="text" value="555-1212"/></td></tr>';
         dialogBody += '<tr><td>Message</td><td><input name="message" type="text" value="Test Message"/></td></tr>';
         dialogBody += '</table></form>';
@@ -945,7 +945,6 @@ PWM_CFGEDIT.initSettingDisplay = function(setting, options) {
     PWM_MAIN.addEventHandler('setting-' + settingKey, 'click', function () {
         PWM_CFGEDIT.displaySettingHelp(settingKey);
     });
-
     PWM_MAIN.addEventHandler('resetButton-' + settingKey, 'click', function () {
         handleResetClick(settingKey);
     });
@@ -1006,7 +1005,7 @@ PWM_CFGEDIT.initSettingDisplay = function(setting, options) {
 
         case 'LOCALIZED_STRING':
         case 'LOCALIZED_TEXT_AREA':
-            LocalizedStringValueHandler.init(settingKey, '', setting['syntax']);
+            LocalizedStringValueHandler.init(settingKey);
             break;
 
         case 'USER_PERMISSION':
@@ -1025,6 +1024,13 @@ PWM_CFGEDIT.initSettingDisplay = function(setting, options) {
             FileValueHandler.init(settingKey);
             break;
 
+        case 'VERIFICATION_METHOD':
+            VerificationMethodHandler.init(settingKey);
+            break;
+
+        case 'NONE':
+            break;
+
         default:
             alert('unknown setting syntax type: ' + setting['syntax']);
 
@@ -1172,8 +1178,9 @@ PWM_CFGEDIT.drawDisplayTextPage = function(settingKey, keys) {
         var settingInfo = {};
         settingInfo['key'] = displayKey;
         settingInfo['label'] = keys[keyCounter];
-        settingInfo['syntax'] = 'LOCALIZED_STRING';
+        settingInfo['syntax'] = 'NONE';
         PWM_CFGEDIT.initSettingDisplay(settingInfo);
+        LocalizedStringValueHandler.init(displayKey,{required:true});
         remainingLoads--;
         PWM_MAIN.getObject('remainingCount').innerHTML = remainingLoads > 0 ? remainingLoads : '';
     };

+ 12 - 16
pwm/servlet/web/public/resources/js/configmanager.js

@@ -158,27 +158,23 @@ PWM_CONFIG.showHeaderHealth = function() {
         var loadFunction = function(data) {
             if (data['data'] && data['data']['records']) {
                 var healthRecords = data['data']['records'];
-                var htmlBody = '';
+                var hasWarnTopics = false;
                 for (var i = 0; i < healthRecords.length; i++) {
                     var healthData = healthRecords[i];
                     if (healthData['status'] == 'WARN') {
-                        //require(["dojo/cookie"], function(cookie){
-                        //    var cookieValue = cookie('headerVisibility');
-                        //    if (!cookieValue) {
-                        PWM_MAIN.openHeaderWarningPanel();
-                        //    }
-                        //});
-
-                        htmlBody += '<div class="header-error">';
-                        htmlBody += healthData['status'];
-                        htmlBody += " - ";
-                        htmlBody += healthData['topic'];
-                        htmlBody += " - ";
-                        htmlBody += healthData['detail'];
-                        htmlBody += '</div>';
+                        hasWarnTopics = true;
                     }
                 }
-                parentDiv.innerHTML = htmlBody;
+                if (hasWarnTopics) {
+                    PWM_MAIN.openHeaderWarningPanel();
+                    parentDiv.innerHTML = '<div id="panel-healthHeaderErrors" class="header-error"><span class="fa fa-warning"></span> ' + PWM_ADMIN.showString('Header_ConfigWarningsPresent') + '</div>';
+                    var tooltipBody = PWM_ADMIN.makeHealthHtml(data['data'],true,false);
+                    PWM_MAIN.showTooltip({
+                        position:'below',
+                        id:'panel-healthHeaderErrors',
+                        text:tooltipBody
+                    });
+                }
                 setTimeout(function () {
                     PWM_CONFIG.showHeaderHealth()
                 }, 60 * 1000);

+ 12 - 11
pwm/servlet/web/public/resources/js/main.js

@@ -584,14 +584,6 @@ PWM_MAIN.showTooltip = function(options){
 
 PWM_MAIN.clearDijitWidget = function (widgetName) {
     require(["dojo","dijit/registry"],function(dojo, registry){
-        /*
-         try {
-         registry.byId(widgetName).destroy(true);
-         } catch (e) {
-         console.log("error destroying widget '" + widgetName + '", error: ' + e);
-         }
-         */
-
 
         var oldDijitNode = registry.byId(widgetName);
         if (oldDijitNode != null) {
@@ -790,7 +782,7 @@ PWM_MAIN.showDialog = function(options) {
     }
     if (showOk) {
         bodyText += '<button class="btn" id="dialog_ok_button">'
-        + '<span class="btn-icon fa fa-forward"></span>'
+        + '<span class="btn-icon fa fa-check-square-o"></span>'
         + PWM_MAIN.showString('Button_OK') + '</button>  ';
     }
     if (showCancel) {
@@ -905,6 +897,7 @@ PWM_MAIN.showConfirmDialog = function(options) {
     options = options == undefined ? {} : options;
     options['showCancel'] = true;
     options['title'] = 'title' in options ? options['title'] : PWM_MAIN.showString('Button_Confirm');
+    options['text'] = 'text' in options ? options['text'] : PWM_MAIN.showString('Confirm');
     PWM_MAIN.showDialog(options);
 };
 
@@ -1178,7 +1171,7 @@ PWM_MAIN.messageDivFloatHandler = function() {
 PWM_MAIN.pwmFormValidator = function(validationProps, reentrant) {
     var CONSOLE_DEBUG = false;
 
-    var serviceURL = PWM_MAIN.addPwmFormIDtoURL(validationProps['serviceURL']);
+    var serviceURL = validationProps['serviceURL'];
     var readDataFunction = validationProps['readDataFunction'];
     var processResultsFunction = validationProps['processResultsFunction'];
     var messageWorking = validationProps['messageWorking'] ? validationProps['messageWorking'] : PWM_MAIN.showString('Display_PleaseWait');
@@ -1707,7 +1700,15 @@ PWM_MAIN.addPwmFormIDtoURL = function(url) {
 
 PWM_MAIN.addParamToUrl = function(url,paramName,paramValue) {
     if (!url || url.length < 1) {
-        return '';
+        return url;
+    }
+
+    if (
+        url.indexOf('?' + paramName + '=') > -1
+        || url.indexOf('&' + paramName + '=') > -1)
+    {
+        console.warn('ignoring request to append duplicate param "' + paramName + '" to url ' + url);
+        return url;
     }
 
     var encodedName = encodeURIComponent(paramName);

+ 32 - 0
pwm/servlet/web/public/resources/js/otpsecret.js

@@ -43,6 +43,8 @@ PWM_OTP.checkExistingCode = function() {
                 PWM_MAIN.getObject('checkIcon').style.display = 'none';
                 PWM_MAIN.getObject('crossIcon').style.display = 'inherit';
             }
+            PWM_MAIN.getObject('verifyCodeInput').value = '';
+            PWM_MAIN.getObject('verifyCodeInput').focus();
         },
         completeFunction:function(){
             PWM_MAIN.getObject('button-verifyCode').disabled = false;
@@ -51,7 +53,37 @@ PWM_OTP.checkExistingCode = function() {
     });
 };
 
+PWM_OTP.openCheckCodeDialog = function() {
+    var templateHtml = '<div>'
+        + '<p>' + PWM_MAIN.showString("Display_RecoverOTP") + '</p>'
+        + '<table class="noborder" style="width: 300px; table-layout: fixed">'
+        + '<tr><td style="width:115px">'
+        + '<input type="text" class="inputfield" style="max-width: 100px; width: 100px" pattern="[0-9].*" id="verifyCodeInput" autofocus maxlength="6" />'
+        + '</td><td style="width:20px">'
+        + '<span style="display:none;color:green" id="checkIcon" class="btn-icon fa fa-lg fa-check"></span>'
+        + '<span style="display:none;color:red" id="crossIcon" class="btn-icon fa fa-lg fa-times"></span>'
+        + '<span style="display:none" id="workingIcon" class="fa fa-lg fa-spin fa-spinner"></span>'
+        + '</td><td style="width:150px">'
+        + '<button type="submit" name="button-verifyCode" class="btn" id="button-verifyCode">'
+        + '<span class="btn-icon fa fa-check"></span>' + PWM_MAIN.showString("Button_CheckCode") + '</button>'
+        + '</td></tr></table></div>';
+
+    PWM_MAIN.showDialog({
+        title:PWM_MAIN.showString('Button_CheckCode'),
+        text:templateHtml,
+        showClose:true,
+        loadFunction: function(){
+            PWM_MAIN.addEventHandler('button-verifyCode','click',function(){
+                PWM_OTP.checkExistingCode();
+            });
+        }
+    });
+};
+
 PWM_OTP.initExistingOtpPage = function() {
+    PWM_MAIN.addEventHandler('button-verifyCodeDialog','click',function(){
+        PWM_OTP.openCheckCodeDialog();
+    });
     PWM_MAIN.getObject('continue_button').type = 'button';
     PWM_MAIN.addEventHandler('continue_button','click',function(){
         PWM_MAIN.showConfirmDialog({

+ 10 - 14
pwm/servlet/web/public/resources/style.css

@@ -196,7 +196,8 @@ input[type=password]::-ms-reveal{display: none;}
 }
 
 .menubutton {
-    max-width: 150px;
+    max-width: 135px;
+    width: 135px;
     padding: 4px 11px;
     display: block;
     border-radius: 3px;
@@ -769,21 +770,12 @@ input[type=search] {
 dialog::backdrop { background: rgba(0,0,0,0.6); }
 
 dialog {
-    /*
-    position: absolute;
-    top:0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-
-    margin: auto;
-
-
-    padding-top: 0;
-    */
     border: 2px solid #DDDDDD;
     border-radius: 3px;
     padding:0;
+    animation: fadein 0.3s;
+    -moz-animation: fadein 0.3s; /* Firefox */
+    -webkit-animation: fadein 0.3s; /* Safari and Chrome */
 }
 
 dialog .titleBar {
@@ -809,4 +801,8 @@ dialog .closeIcon {
 progress:not([value]) {
     width: 100%;
     height: 20px;
-}
+}
+
+@keyframes fadein { from { opacity:0; } to { opacity:1; } }
+@-moz-keyframes fadein { from { opacity:0 } to { opacity:1 } }
+@-webkit-keyframes fadein { from { opacity:0 } to { opacity:1 } }

+ 17 - 0
pwm/servlet/web/public/resources/themes/water/style.css

@@ -167,3 +167,20 @@ table td.key {
     border-top-color: #070618;
     background: #717081;
 }
+
+.menubutton_key {
+    color: white;
+}
+
+.menubutton {
+    color: white;
+    text-align: right;
+}
+
+.agreementText {
+    background-color: #444444;
+}
+
+.checkboxWrapper {
+    background-color: #444444;
+}