Browse Source

configeditor updates, mobile ui improvements

jrivard 10 years ago
parent
commit
479bd96f7c
61 changed files with 998 additions and 858 deletions
  1. 3 2
      pwm/servlet/src/password/pwm/AppProperty.java
  2. 5 4
      pwm/servlet/src/password/pwm/AppProperty.properties
  3. 1 0
      pwm/servlet/src/password/pwm/PwmConstants.java
  4. 0 6
      pwm/servlet/src/password/pwm/config/ActionConfiguration.java
  5. 8 4
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  6. 86 91
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  7. 1 1
      pwm/servlet/src/password/pwm/config/StoredConfiguration.java
  8. 1 1
      pwm/servlet/src/password/pwm/config/value/EmailValue.java
  9. 1 1
      pwm/servlet/src/password/pwm/http/PwmHttpRequestWrapper.java
  10. 4 4
      pwm/servlet/src/password/pwm/http/PwmRequest.java
  11. 4 0
      pwm/servlet/src/password/pwm/http/PwmURL.java
  12. 5 0
      pwm/servlet/src/password/pwm/http/filter/ApplicationModeFilter.java
  13. 10 3
      pwm/servlet/src/password/pwm/http/filter/AuthenticationFilter.java
  14. 39 17
      pwm/servlet/src/password/pwm/http/filter/SessionFilter.java
  15. 24 11
      pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java
  16. 5 0
      pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java
  17. 5 1
      pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java
  18. 3 3
      pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java
  19. 30 4
      pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java
  20. 3 1
      pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java
  21. 106 38
      pwm/servlet/src/password/pwm/http/servlet/OAuthConsumerServlet.java
  22. 8 3
      pwm/servlet/src/password/pwm/http/servlet/PeopleSearchServlet.java
  23. 1 1
      pwm/servlet/src/password/pwm/http/servlet/PwmServlet.java
  24. 1 1
      pwm/servlet/src/password/pwm/http/servlet/UpdateProfileServlet.java
  25. 1 1
      pwm/servlet/src/password/pwm/i18n/Display.properties
  26. 1 1
      pwm/servlet/src/password/pwm/i18n/Error.properties
  27. 2 11
      pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java
  28. 3 2
      pwm/servlet/src/password/pwm/ldap/auth/AuthenticationRequest.java
  29. 6 0
      pwm/servlet/src/password/pwm/util/ServletHelper.java
  30. 108 22
      pwm/servlet/src/password/pwm/util/cli/MainClass.java
  31. 2 0
      pwm/servlet/src/password/pwm/util/localdb/LocalDBUtility.java
  32. 4 3
      pwm/servlet/src/password/pwm/util/logging/PwmLogManager.java
  33. 8 2
      pwm/servlet/src/password/pwm/util/logging/PwmLogger.java
  34. 75 0
      pwm/servlet/src/password/pwm/util/macro/StandardMacros.java
  35. 8 12
      pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java
  36. 11 0
      pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java
  37. 1 0
      pwm/servlet/web/WEB-INF/jsp/admin-logview.jsp
  38. 26 53
      pwm/servlet/web/WEB-INF/jsp/captcha.jsp
  39. 2 2
      pwm/servlet/web/WEB-INF/jsp/changepassword-form.jsp
  40. 1 1
      pwm/servlet/web/WEB-INF/jsp/changepassword-wait.jsp
  41. 1 1
      pwm/servlet/web/WEB-INF/jsp/configeditor.jsp
  42. 30 105
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap.jsp
  43. 4 5
      pwm/servlet/web/WEB-INF/jsp/forgottenpassword-responses.jsp
  44. 1 1
      pwm/servlet/web/WEB-INF/jsp/fragment/form.jsp
  45. 1 1
      pwm/servlet/web/WEB-INF/jsp/login-passwordonly.jsp
  46. 2 3
      pwm/servlet/web/WEB-INF/jsp/newuser-wait.jsp
  47. 3 6
      pwm/servlet/web/WEB-INF/jsp/newuser.jsp
  48. 12 11
      pwm/servlet/web/WEB-INF/jsp/peoplesearch.jsp
  49. 1 11
      pwm/servlet/web/WEB-INF/web.xml
  50. 1 1
      pwm/servlet/web/private/index.jsp
  51. 2 1
      pwm/servlet/web/public/reference/license.jsp
  52. 2 1
      pwm/servlet/web/public/resources/configStyle.css
  53. 6 0
      pwm/servlet/web/public/resources/js/admin.js
  54. 248 239
      pwm/servlet/web/public/resources/js/configeditor-settings.js
  55. 14 18
      pwm/servlet/web/public/resources/js/configguide.js
  56. 4 2
      pwm/servlet/web/public/resources/js/configmanager.js
  57. 2 3
      pwm/servlet/web/public/resources/js/main.js
  58. 2 2
      pwm/servlet/web/public/resources/js/peoplesearch.js
  59. 30 4
      pwm/servlet/web/public/resources/mobileStyle.css
  60. 9 135
      pwm/servlet/web/public/resources/style.css
  61. 10 1
      pwm/servlet/web/public/resources/text/macroHelp.html

+ 3 - 2
pwm/servlet/src/password/pwm/AppProperty.java

@@ -43,13 +43,14 @@ public enum AppProperty {
     CLIENT_WARNING_HEADER_SHOW                      ("client.warningHeader.show"),
     CLIENT_PW_SHOW_REVERT_TIMEOUT                   ("client.pwShowRevertTimeout"),
     CLIENT_JSP_SHOW_ICONS                           ("client.jsp.showIcons"),
-    CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT           ("configEditor.queryFilter.testLimit"),
     CONFIG_MAX_JDBC_JAR_SIZE                        ("config.maxJdbcJarSize"),
     CONFIG_RELOAD_ON_CHANGE                         ("config.reloadOnChange"),
     CONFIG_MAX_PERSISTENT_LOGIN_SECONDS             ("config.maxPersistentLoginSeconds"),
     CONFIG_FILE_SCAN_FREQUENCY                      ("config.fileScanFrequencyMS"),
     CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS         ("config.newuser.passwordPolicyCacheMS"),
-    CONFIG_GUIDE_IDLE_TIMEOUT                       ("config.guide.idleTimeoutSeconds"),
+    CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT           ("configEditor.queryFilter.testLimit"),
+    CONFIG_EDITOR_IDLE_TIMEOUT                      ("configEditor.idleTimeoutSeconds"),
+    CONFIG_GUIDE_IDLE_TIMEOUT                       ("configGuide.idleTimeoutSeconds"),
     FORM_EMAIL_REGEX                                ("form.email.regexTest"),
     HTTP_RESOURCES_MAX_CACHE_ITEMS                  ("http.resources.maxCacheItems"),
     HTTP_RESOURCES_MAX_CACHE_BYTES                  ("http.resources.maxCacheBytes"),

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

@@ -37,13 +37,14 @@ client.formNonce.length=10
 client.warningHeader.show=true
 client.pwShowRevertTimeout=45000
 client.jsp.showIcons=true
-configEditor.queryFilter.testLimit=1000
 config.reloadOnChange=true
 config.maxJdbcJarSize=10240000
 config.maxPersistentLoginSeconds=3600
 config.fileScanFrequencyMS=5017
 config.newuser.passwordPolicyCacheMS=3600000
-config.guide.idleTimeoutSeconds=3600
+configEditor.queryFilter.testLimit=1000
+configEditor.idleTimeoutSeconds=900
+configGuide.idleTimeoutSeconds=3600
 form.email.regexTest=^[_+a-zA-Z0-9-]+(\\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*$
 health.minimumCheckIntervalSeconds=60
 health.certificate.warnSeconds=2592000
@@ -150,9 +151,9 @@ queue.syslog.maxAgeMs=86400000
 queue.syslog.maxCount=100000
 queue.maxCloseTimeoutMs=5000
 reporting.ldap.searchTimeoutMs=300000
-recaptcha.clientJsUrl=//www.google.com/recaptcha/api/js/recaptcha_ajax.js
+recaptcha.clientJsUrl=//www.google.com/recaptcha/api.js
 recaptcha.clientIframeUrl=//www.google.com/recaptcha/api/noscript
-recaptcha.validateUrl=http://www.google.com/recaptcha/api/verify
+recaptcha.validateUrl=https://www.google.com/recaptcha/api/siteverify
 security.html.stripInlineJavascript=false
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.responses.hashIterations=100000

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

@@ -149,6 +149,7 @@ public abstract class PwmConstants {
         CaptchaIframeUrl,
         CaptchaPublicKey,
 
+        ForgottenPasswordChallengeSet,
         ForgottenPasswordOptionalPageView
     }
 

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

@@ -33,7 +33,6 @@ 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;
@@ -44,7 +43,6 @@ 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;
@@ -82,10 +80,6 @@ public class ActionConfiguration implements Serializable {
         return body;
     }
 
-    public BodyEncoding getBodyEncoding() {
-        return bodyEncoding;
-    }
-
     public String getAttributeName() {
         return attributeName;
     }

+ 8 - 4
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -808,6 +808,8 @@ public enum PwmSetting {
             "peopleSearch.searchBase", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH),
     PEOPLE_SEARCH_ENABLE_PUBLIC(
             "peopleSearch.enablePublic", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH),
+    PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS(
+            "peopleSearch.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH),
 
 
 
@@ -887,6 +889,8 @@ public enum PwmSetting {
             "helpdesk.enforcePasswordPolicy", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_RESPONSES(
             "helpdesk.clearResponses", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
+    HELPDESK_FORCE_PW_EXPIRATION(
+            "helpdesk.forcePwExpiration", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_RESPONSES_BUTTON(
             "helpdesk.clearResponses.button", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_OTP_BUTTON(
@@ -957,7 +961,7 @@ public enum PwmSetting {
     // CAS SSO
     CAS_CLEAR_PASS_URL(
             "cas.clearPassUrl", PwmSettingSyntax.STRING, PwmSettingCategory.CAS_SSO),
-    
+
     // http sso
     SSO_AUTH_HEADER_NAME(
             "security.sso.authHeaderName", PwmSettingSyntax.STRING, PwmSettingCategory.HTTP_SSO),
@@ -991,7 +995,7 @@ public enum PwmSetting {
             "webservice.userAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.REST_CLIENT),
 
 
-    
+
     // deprecated.
     PASSWORD_POLICY_AD_COMPLEXITY(
             "password.policy.ADComplexity", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY),
@@ -1000,8 +1004,8 @@ public enum PwmSetting {
     FORGOTTEN_PASSWORD_REQUIRE_OTP(
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
 
-    
-    
+
+
     ;
 
 // ------------------------------ STATICS ------------------------------

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

@@ -1329,16 +1329,12 @@
     <setting key="password.policy.regExMatch" level="2">
         <label>Required Regular Expression Matches</label>
         <description><![CDATA[A Regular Expression pattern the password must match in order to be allowed.  Multiple patterns can be listed.  A pattern must match the <i>entire</i> password to be applied.  A partial match is ignored.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="password.policy.regExNoMatch" level="2">
         <label>Disallowed Regular Expression Matches</label>
         <description><![CDATA[A Regular Expression pattern the password must <b>not</b> match in order to be allowed.  Multiple patterns can be listed.  A pattern must match the <i>entire</i> password to be applied.  A partial match is ignored.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="password.policy.disallowedValues" level="1">
         <label>Disallowed Values</label>
@@ -1389,9 +1385,7 @@
     <setting key="password.policy.changeMessage" level="1">
         <label>Password Change Message</label>
         <description><![CDATA[Message to be displayed to user during password changes.  May include HTML markup.  This setting may be overwritten by a change password message read as part of an ldap password policy.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="password.policy.ruleText" level="2">
         <label>Password Rule Text</label>
@@ -1765,9 +1759,8 @@
     <setting key="email.adminAlert.toAddress" level="1">
         <label>System Audit Event Email Alerts</label>
         <description><![CDATA[Send an email when System Audit events occur to these email addresses.]]></description>
-        <default>
-            <value><![CDATA[admin@example.com]]></value>
-        </default>
+        <default/>
+        <placeholder><![CDATA[admin@example.com]]></placeholder>
     </setting>
     <setting key="audit.system.eventList" level="1">
         <label>System Audit Event Types</label>
@@ -1791,9 +1784,8 @@
     <setting key="audit.userEvent.toAddress" level="1">
         <label>User Audit Event Email Alerts</label>
         <description><![CDATA[Send an email User Audit events occur to these email addresses.]]></description>
-        <default>
-            <value><![CDATA[]]></value>
-        </default>
+        <default/>
+        <placeholder><![CDATA[admin@example.com]]></placeholder>
     </setting>
     <setting key="audit.user.eventList" level="1">
         <label>User Audit Event Types</label>
@@ -2157,62 +2149,60 @@
         <label>Random Questions</label>
         <description><![CDATA[Random Questions for Challenge/Response.   Some of these questions will be presented to the user during forgotten password - the number set in the "Minimum Password Required" setting.  The user may be required to supply answers to all or some of these questions when setting up their responses, this is controlled by the "Minimum Random Challenges Required During Setup" setting.]]></description>
         <default>
-            <value><![CDATA[{"text":"What is the name of the main character in your favorite book?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is the name of your favorite teacher?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is the name of your favorite pet?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What was the name of your childhood best friend?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What was your favorite show as a child?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"Who is your favorite author?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is your favorite food?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is your partner's nickname?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is your favorite team?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What street did you grow up on?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What city / town were you born in?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is your favorite vehicle?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"If you could meet someone from history, who would it be?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What is your least favorite film of all time?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"Who was your least favorite teacher?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value><![CDATA[{"text":"What food do you dislike the most?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est le nom du personnage principal de votre livre préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est le nom de votre professeur préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est le nom de votre animal préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel était le nom de votre meilleur ami d'enfance?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel a été votre émission préférée comme un enfant?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Qui est votre auteur préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est votre plat préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est le surnom de votre partenaire?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quelle est votre équipe préférée?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Dans quelle rue avez-vous grandi?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Dans quelle ville / ville êtes-vous né?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est votre véhicule préféré?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Si vous pouviez rencontrer quelqu'un de l'histoire, ce serait qui?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quel est ton film préféré moins de tous les temps?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Qui était votre professeur préféré moins?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="fr"><![CDATA[{"text":"Quels sont les aliments que vous aimez le plus?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Hoe heet de hoofdpersoon in uw favoriete boek?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Hoe heet uw favoriete leraar?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Hoe heet uw favoriete huisdier?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Hoe heette uw beste vriend(in) uit uw jeugd?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat was uw favoriete TV-programma in uw jeugd?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wie is uw favoriete schrijver?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat is uw lievelingseten?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat is de bijnaam van uw partner?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete team?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"In welke straat bent u opgegroeid?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"In welke stad of plaats bent u geboren?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete voertuig?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Als u een historisch persoon zou kunnen ontmoeten, wie zou dat zijn?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete film aller tijden?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Wie was uw minst geliefde leraar?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
-            <value locale="nl"><![CDATA[{"text":"Van welk eten houdt u het minst?","minLength":4,"maxLength":200,"adminDefined":true}]]></value>
+            <value><![CDATA[{"text":"What is the name of the main character in your favorite book?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is the name of your favorite teacher?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is the name of your favorite pet?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What was the name of your childhood best friend?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What was your favorite show as a child?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"Who is your favorite author?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is your favorite food?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is your partner's nickname?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is your favorite team?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What street did you grow up on?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What city / town were you born in?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is your favorite vehicle?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"If you could meet someone from history, who would it be?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What is your least favorite film of all time?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"Who was your least favorite teacher?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value><![CDATA[{"text":"What food do you dislike the most?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est le nom du personnage principal de votre livre préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est le nom de votre professeur préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est le nom de votre animal préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel était le nom de votre meilleur ami d'enfance?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel a été votre émission préférée comme un enfant?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Qui est votre auteur préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est votre plat préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est le surnom de votre partenaire?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quelle est votre équipe préférée?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Dans quelle rue avez-vous grandi?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Dans quelle ville / ville êtes-vous né?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est votre véhicule préféré?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Si vous pouviez rencontrer quelqu'un de l'histoire, ce serait qui?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quel est ton film préféré moins de tous les temps?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Qui était votre professeur préféré moins?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="fr"><![CDATA[{"text":"Quels sont les aliments que vous aimez le plus?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Hoe heet de hoofdpersoon in uw favoriete boek?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Hoe heet uw favoriete leraar?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Hoe heet uw favoriete huisdier?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Hoe heette uw beste vriend(in) uit uw jeugd?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat was uw favoriete TV-programma in uw jeugd?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wie is uw favoriete schrijver?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat is uw lievelingseten?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat is de bijnaam van uw partner?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete team?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"In welke straat bent u opgegroeid?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"In welke stad of plaats bent u geboren?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete voertuig?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Als u een historisch persoon zou kunnen ontmoeten, wie zou dat zijn?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wat is uw favoriete film aller tijden?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Wie was uw minst geliefde leraar?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
+            <value locale="nl"><![CDATA[{"text":"Van welk eten houdt u het minst?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
         </default>
     </setting>
     <setting key="challenge.requiredChallenges" level="1">
         <label>Required Questions</label>
         <description><![CDATA[Required Questions for Challenge/Response.  The user must supply answers for all of these questions when setting up their responses.  Additionally, answers to these questions must be supplied during forgotten password.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="challenge.minRandomRequired" level="1" required="true">
         <label>Minimum Random Required</label>
@@ -2252,16 +2242,12 @@
     <setting key="challenge.helpdesk.randomChallenges" level="1">
         <label>Helpdesk Random Questions</label>
         <description><![CDATA[The user may be required to supply answers to all or some of these questions when setting up their responses, as controlled by the "Minimum Helpdesk Random Challenges Required During Setup" setting.  The questions and answers will be visible to helpdesk users, but are not used for forgotten password recovery.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="challenge.helpdesk.requiredChallenges" level="1">
         <label>Helpdesk Required Questions</label>
         <description><![CDATA[The user must supply answers for all of these questions when setting up their responses.  The questions and answers will be visible to helpdesk users, but are not used for forgotten password recovery.]]></description>
-        <default>
-            <value />
-        </default>
+        <default/>
     </setting>
     <setting key="challenge.helpdesk.minRandomsSetup" level="1" required="true">
         <label>Minimum Helpdesk Random Challenges Required During Setup</label>
@@ -2310,7 +2296,7 @@
         <label>Verification Methods</label>
         <description><![CDATA[Verification Methods]]></description>
         <default>
-                <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
+            <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
         </default>
     </setting>
     <setting key="recovery.enable" level="1">
@@ -3048,6 +3034,13 @@
             <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
         </default>
     </setting>
+    <setting key="peopleSearch.idleTimeout" level="1">
+        <label>Idle Timeout Seconds for PeopleSearch Users</label>
+        <description><![CDATA[Number of seconds after which an authenticated session becomes unauthenticated.   Session Idle timeout will be set to this value once a user successfully access the people search module.]]></description>
+        <default>
+            <value>3600</value>
+        </default>
+    </setting>
     <setting key="ldap.edirectory.enableNmas" level="1" required="true">
         <label>Enable NMAS Extensions</label>
         <description><![CDATA[When connecting to a NetIQ eDirectory LDAP directory, this parameter will control if NMAS extensions will be used when connecting to the ldap directory.  Enabling nmas results in:<ul><li>better error messages when using universal password policies</li><li>better error handling during certain change password scenarios</li></ul>Unless you are using an older version of eDirectory (pre 8.8 or before), it is generally best to set this to true.<br/><br/>All NMAS operations require an SSL connection to the directory.]]></description>
@@ -3089,7 +3082,7 @@
         </default>
     </setting>
     <setting key="ldap.edirectory.readChallengeSets" level="1" required="true">
-        <label>Read Challenge Sets</label>
+        <label>Read eDirectory Challenge Sets</label>
         <description><![CDATA[If true, the challenge set configuration from eDirectory Universal Password policy will be read and applied to users.  If an eDirectory challenge set is applied to the user, that policy will be used, otherwise the policy that is a part of this configuration will be used.  To require only NMAS configured challenge sets, be sure to blank out the required and forgotten questions as part of this configuration, or else those will be used in cases where no eDirectory policy.]]></description>
         <default>
             <value>false</value>
@@ -3106,21 +3099,21 @@
         </default>
     </setting>
     <setting key="ldap.edirectory.cr.minRandomDuringSetup" level="1" required="true">
-        <label>eDir CR Minimum Randoms During Setup</label>
+        <label>eDirectory Challenge Set Minimum Randoms During Setup</label>
         <description><![CDATA[]]></description>
         <default>
             <value>0</value>
         </default>
     </setting>
     <setting key="ldap.edirectory.cr.applyWordlist" level="1" required="true">
-        <label>eDir CR Apply Wordlist</label>
+        <label>eDirectory Challenge Set Apply Wordlist</label>
         <description><![CDATA[]]></description>
         <default>
             <value>false</value>
         </default>
     </setting>
     <setting key="ldap.edirectory.cr.maxQuestionCharsInAnswer" level="1" required="true">
-        <label>eDir CR Maximum Question Chars In Answer</label>
+        <label>eDirectory Challenge Set Maximum Question Chars In Answer</label>
         <description><![CDATA[]]></description>
         <default>
             <value>0</value>
@@ -3180,13 +3173,8 @@
     </setting>
     <setting key="helpdesk.filter" level="1">
         <label>Helpdesk Search Filter</label>
-        <description><![CDATA[LDAP search filter to query the directory with.  Substitute <i>%USERNAME%</i> for user supplied username]]></description>
-        <default>
-            <value><![CDATA[(&(objectClass=Person)(|((cn=*%USERNAME%*)(uid=*%USERNAME%*)(givenName=*%USERNAME%*)(sn=*%USERNAME%*))))]]></value>
-        </default>
-        <default template="AD">
-            <value><![CDATA[(&(objectClass=Person)(|((cn=*%USERNAME%*)(uid=*%USERNAME%*)(sAMAccountName=*%USERNAME%*)(userprincipalname=*%USERNAME%*)(givenName=*%USERNAME%*)(sn=*%USERNAME%*))))]]></value>
-        </default>
+        <description><![CDATA[LDAP search filter to query the directory with.  Substitute <i>%USERNAME%</i> for user supplied username.  If not specified, a search filter will be auto calculated based on the Helpdesk Search Form.<p>Examples<ul><li>Edirectory: (&(objectClass=Person)(|((cn=*%USERNAME%*)(uid=*%USERNAME%*)(givenName=*%USERNAME%*)(sn=*%USERNAME%*))))</li><li>Active Directory:(&(objectClass=Person)(|((cn=*%USERNAME%*)(uid=*%USERNAME%*)(sAMAccountName=*%USERNAME%*)(userprincipalname=*%USERNAME%*)(givenName=*%USERNAME%*)(sn=*%USERNAME%*))))</li></ul>]]></description>
+        <default/>
     </setting>
     <setting key="helpdesk.result.form" level="1" required="true">
         <label>Helpdesk Search Form</label>
@@ -3386,6 +3374,13 @@
             <option value="no">False</option>
         </options>
     </setting>
+    <setting key="helpdesk.forcePwExpiration" level="1">
+        <label>Force Password Expiration On Password Set</label>
+        <description><![CDATA[]]></description>
+        <default>
+            <value>false</value>
+        </default>
+    </setting>
     <setting key="helpdesk.clearOtp.button" level="2" hidden="false">
         <label>Enable Clear One Time Password Settings Button</label>
         <description><![CDATA[Allow the helpdesk operator to clear out a user's stored one time password settings by clicking a button.]]></description>
@@ -3676,26 +3671,26 @@
         <label>External Web Services Permissions</label>
         <description><![CDATA[Users permitted to execute REST web services.]]></description>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,ou=Groups,o=example)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,ou=Groups,o=example"}]]></value>
         </default>
         <default syntaxVersion="2" template="AD">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,cn=Users,DC=site,DC=example,DC=net)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,cn=Users,DC=site,DC=example,DC=net"}]]></value>
         </default>
         <default syntaxVersion="2" template="ORACLE_DS">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,cn=Users,DC=site,DC=example,DC=net)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=WebServiceUsers,cn=Users,DC=site,DC=example,DC=net"}]]></value>
         </default>
     </setting>
     <setting key="webservices.thirdParty.queryMatch" level="2">
         <label>Web Services Third Party Permissions</label>
         <description><![CDATA[Users permitted to execute REST web services and specify a third party via the 'username' parameter.]]></description>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,ou=Groups,o=example)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,ou=Groups,o=example"}]]></value>
         </default>
         <default syntaxVersion="2" template="AD">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,cn=Users,DC=site,DC=example,DC=net)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,cn=Users,DC=site,DC=example,DC=net"}]]></value>
         </default>
         <default syntaxVersion="2" template="ORACLE_DS">
-            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,cn=Users,DC=site,DC=example,DC=net)"}]]></value>
+            <value><![CDATA[{"type":"ldapGroup","ldapProfileID":"all","ldapBase":"cn=ThirdPartyWebServiceUsers,cn=Users,DC=site,DC=example,DC=net"}]]></value>
         </default>
     </setting>
     <setting key="external.macros.urls" level="2">
@@ -4144,7 +4139,7 @@
         <description><![CDATA[NetIQ eDirectory specific settings.]]></description>
     </category>
     <category key="EDIR_CR_SETTINGS">
-        <label>eDirectory CR Settings</label>
+        <label>eDirectory Challenge Sets</label>
         <description><![CDATA[NetIQ eDirectory CR specific settings.]]></description>
     </category>
     <category key="ACTIVE_DIRECTORY">

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

@@ -1240,7 +1240,7 @@ public class StoredConfiguration implements Serializable {
                         output.append("<div class=\"changeLogKey\">");
                         output.append(keyName);
                         output.append("</div><div class=\"changeLogValue\">");
-                        output.append(value);
+                        output.append(StringUtil.escapeHtml(value));
                         output.append("</div>");
                     } else {
                         output.append(keyName);

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

@@ -64,7 +64,7 @@ public class EmailValue extends AbstractValue implements StoredValue {
             )
                     throws PwmOperationalException
             {
-                final Map<String, EmailItemBean> values = new HashMap<>();
+                final Map<String, EmailItemBean> values = new TreeMap<>();
                 {
                     final List valueElements = settingElement.getChildren("value");
                     for (final Object loopValue : valueElements) {

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

@@ -138,7 +138,7 @@ public abstract class PwmHttpRequestWrapper {
         final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
         
         final String rawValue = httpServletRequest.getParameter(name);
-        if (rawValue != null) {
+        if (rawValue != null && !rawValue.isEmpty()) {
             final String decodedValue = decodeStringToDefaultCharSet(rawValue);
             final String sanitizedValue = Validator.sanitizeInputValue(configuration, decodedValue, maxLength);
             if (sanitizedValue != null) {

+ 4 - 4
pwm/servlet/src/password/pwm/http/PwmRequest.java

@@ -25,7 +25,6 @@ package password.pwm.http;
 import org.apache.commons.fileupload.FileItemIterator;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.h2.util.StringUtils;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.Validator;
@@ -84,6 +83,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         HIDE_HEADER_BUTTONS,
         HIDE_HEADER_WARNINGS,
         NO_REQ_COUNTER,
+        ALWAYS_EXPAND_MESSAGE_TEXT,
     }
 
     public static PwmRequest forRequest(
@@ -339,15 +339,15 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
             return false;
         }
 
+        final String tokenValue = aftPath; // note this value is still urlencoded - the servlet container does not decode path values.
+
         final StringBuilder redirectURL = new StringBuilder();
         redirectURL.append(this.getHttpServletRequest().getContextPath());
         redirectURL.append(this.getHttpServletRequest().getServletPath());
         redirectURL.append("?");
         redirectURL.append(PwmConstants.PARAM_ACTION_REQUEST).append("=enterCode");
         redirectURL.append("&");
-        redirectURL.append(PwmConstants.PARAM_TOKEN).append("=").append(StringUtils.urlEncode(aftPath));
-        redirectURL.append("&");
-        redirectURL.append(PwmConstants.PARAM_FORM_ID).append("=").append(Helper.buildPwmFormID(pwmSession.getSessionStateBean()));
+        redirectURL.append(PwmConstants.PARAM_TOKEN).append("=").append(tokenValue);
 
         LOGGER.debug(pwmSession, "detected long servlet url, redirecting user to " + redirectURL);
         sendRedirect(redirectURL.toString());

+ 4 - 0
pwm/servlet/src/password/pwm/http/PwmURL.java

@@ -84,6 +84,10 @@ public class PwmURL {
         return checkIfStartsWithURL("/public/" + PwmConstants.URL_SERVLET_NEW_USER);
     }
 
+    public boolean isOauthConsumer() {
+        return checkIfStartsWithURL("/public/" + PwmConstants.URL_SERVLET_OAUTH_CONSUMER);
+    }
+
     public boolean isPrivateUrl() {
         return checkIfStartsWithURL("/private/");
     }

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

@@ -103,6 +103,11 @@ public class ApplicationModeFilter extends AbstractPwmFilter {
             return true;
         }
 
+        // allow oauth
+        if (pwmURL.isOauthConsumer()) {
+            return false;
+        }
+
         // block if public request and not running or in trial
         if (!PwmConstants.TRIAL_MODE) {
             if (pwmURL.isPublicUrl() && !pwmURL.isLogoutURL() && !pwmURL.isCommandServletURL()) {

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

@@ -223,7 +223,6 @@ public class AuthenticationFilter extends AbstractPwmFilter {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-        final SessionStateBean ssBean = pwmSession.getSessionStateBean();
 
         //try to authenticate user with basic auth
         if (!pwmSession.getSessionStateBean().isAuthenticated()) {
@@ -405,7 +404,7 @@ public class AuthenticationFilter extends AbstractPwmFilter {
     static boolean processOAuthAuthenticationRequest(
             final PwmRequest pwmRequest
     )
-            throws IOException, ServletException
+            throws IOException, ServletException, PwmUnrecoverableException
 
     {
         final Configuration config = pwmRequest.getConfig();
@@ -414,7 +413,13 @@ public class AuthenticationFilter extends AbstractPwmFilter {
             return false;
         }
 
-        final String state = pwmRequest.getPwmSession().getSessionStateBean().getSessionVerificationKey();
+        final String originalURL;
+        {
+            final HttpServletRequest req = pwmRequest.getHttpServletRequest();
+            originalURL = req.getRequestURI() + (req.getQueryString() != null ? ('?' + req.getQueryString()) : "");
+        }
+
+        final String state = OAuthConsumerServlet.makeStateStringForRequest(pwmRequest, originalURL);
         final String redirectUri = OAuthConsumerServlet.figureOauthSelfEndPointUrl(pwmRequest);
         final String code = config.readAppProperty(AppProperty.OAUTH_ID_REQUEST_TYPE);
 
@@ -426,6 +431,8 @@ public class AuthenticationFilter extends AbstractPwmFilter {
 
         final String redirectUrl = ServletHelper.appendAndEncodeUrlParameters(settings.getLoginURL(), urlParams);
 
+        LOGGER.trace(pwmRequest, "preparing to start oauth authentication request sequence, set originally requested url: " + originalURL);
+
         try{
             pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress(true);
             pwmRequest.sendRedirect(redirectUrl);

+ 39 - 17
pwm/servlet/src/password/pwm/http/filter/SessionFilter.java

@@ -68,9 +68,39 @@ public class SessionFilter extends AbstractPwmFilter {
             final PwmRequest pwmRequest,
             final PwmFilterChain chain
     )
-            throws IOException, ServletException, PwmUnrecoverableException {
+            throws IOException, ServletException, PwmUnrecoverableException
+    {
+        boolean continueFilter = true;
+        {
+            final PwmURL pwmURL = pwmRequest.getURL();
+            if (!pwmURL.isWebServiceURL() && !pwmURL.isResourceURL()) {
+                continueFilter = handleStandardRequestOperations(pwmRequest);
+            }
+        }
+
+        if (!continueFilter) {
+            return;
+        }
+
+        try {
+            chain.doFilter();
+        } catch (Exception e) {
+            LOGGER.warn(pwmRequest.getPwmSession(), "unhandled exception", e);
+            throw new ServletException(e);
+        } catch (Throwable e) {
+            LOGGER.warn(pwmRequest.getPwmSession(), "unhandled exception " + e.getMessage(), e);
+            throw new ServletException(e);
+        }
+    }
+
+    private boolean handleStandardRequestOperations(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException, IOException, ServletException
+    {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final Configuration config = pwmRequest.getConfig();
+
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final SessionStateBean ssBean = pwmSession.getSessionStateBean();
         final PwmResponse resp = pwmRequest.getPwmResponse();
@@ -85,7 +115,7 @@ public class SessionFilter extends AbstractPwmFilter {
             if (PwmError.ERROR_INTRUDER_SESSION != e.getError()) {
                 pwmRequest.invalidateSession();
             }
-            return;
+            return false;
         }
 
         // mark last url
@@ -110,7 +140,7 @@ public class SessionFilter extends AbstractPwmFilter {
             LOGGER.warn("invalidating session due to dirty page leave time greater then configured timeout");
             pwmRequest.invalidateSession();
             resp.sendRedirect(pwmRequest.getHttpServletRequest().getRequestURI());
-            return;
+            return false;
         }
 
         //override session locale due to parameter
@@ -122,7 +152,7 @@ public class SessionFilter extends AbstractPwmFilter {
         // make sure connection is secure.
         if (config.readSettingAsBoolean(PwmSetting.REQUIRE_HTTPS) && !pwmRequest.getHttpServletRequest().isSecure()) {
             pwmRequest.respondWithError(PwmError.ERROR_SECURE_REQUEST_REQUIRED.toInfo());
-            return;
+            return false;
         }
 
         //check for session verification failure
@@ -134,7 +164,7 @@ public class SessionFilter extends AbstractPwmFilter {
                 ssBean.setSessionVerified(true);
             } else {
                 if (verifySession(pwmRequest, mode)) {
-                    return;
+                    return false;
                 }
             }
         }
@@ -147,7 +177,7 @@ public class SessionFilter extends AbstractPwmFilter {
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
-                    return;
+                    return false;
                 }
                 ssBean.setForwardURL(forwardURL);
                 LOGGER.debug(pwmRequest, "forwardURL parameter detected in request, setting session forward url to " + forwardURL);
@@ -163,7 +193,7 @@ public class SessionFilter extends AbstractPwmFilter {
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
-                    return;
+                    return false;
                 }
                 ssBean.setLogoutURL(logoutURL);
                 LOGGER.debug(pwmRequest, "logoutURL parameter detected in request, setting session logout url to " + logoutURL);
@@ -190,22 +220,14 @@ public class SessionFilter extends AbstractPwmFilter {
             ServletHelper.addPwmResponseHeaders(pwmRequest, true);
         }
 
-        try {
-            chain.doFilter();
-        } catch (Exception e) {
-            LOGGER.warn(pwmSession, "unhandled exception", e);
-            throw new ServletException(e);
-        } catch (Throwable e) {
-            LOGGER.warn(pwmSession, "unhandled exception " + e.getMessage(), e);
-            throw new ServletException(e);
-        }
-
         // update last request time.
         ssBean.setSessionLastAccessedTime(new Date());
 
         if (pwmApplication.getStatisticsManager() != null) {
             pwmApplication.getStatisticsManager().incrementValue(Statistic.HTTP_REQUESTS);
         }
+
+        return true;
     }
 
     public void destroy() {

+ 24 - 11
pwm/servlet/src/password/pwm/http/servlet/CaptchaServlet.java

@@ -22,6 +22,9 @@
 
 package password.pwm.http.servlet;
 
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -37,6 +40,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClientRequest;
 import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.util.JsonUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.ServletHelper;
 import password.pwm.util.TimeDuration;
@@ -46,8 +50,10 @@ import password.pwm.util.stats.StatisticsManager;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 
 
 public class CaptchaServlet extends PwmServlet {
@@ -154,13 +160,11 @@ public class CaptchaServlet extends PwmServlet {
         final PasswordData privateKey = pwmApplication.getConfig().readSettingAsPassword(PwmSetting.RECAPTCHA_KEY_PRIVATE);
 
         final StringBuilder bodyText = new StringBuilder();
-        bodyText.append("privatekey=").append(privateKey.getStringValue());
+        bodyText.append("secret=").append(privateKey.getStringValue());
         bodyText.append("&");
         bodyText.append("remoteip=").append(pwmRequest.getSessionLabel().getSrcAddress());
         bodyText.append("&");
-        bodyText.append("challenge=").append(pwmRequest.readParameterAsString("recaptcha_challenge_field"));
-        bodyText.append("&");
-        bodyText.append("response=").append(pwmRequest.readParameterAsString("recaptcha_response_field"));
+        bodyText.append("response=").append(pwmRequest.readParameterAsString("g-recaptcha-response"));
 
         try {
             final PwmHttpClientRequest clientRequest = new PwmHttpClientRequest(
@@ -180,15 +184,24 @@ public class CaptchaServlet extends PwmServlet {
                 ));
             }
 
-            final String[] splitResponse = clientResponse.getBody().split("\n");
-            if (splitResponse.length > 0 && Boolean.parseBoolean(splitResponse[0])) {
-                return true;
+            final JsonElement responseJson = new JsonParser().parse(clientResponse.getBody());
+            final JsonObject topObject = responseJson.getAsJsonObject();
+            if (topObject != null && topObject.has("success")) {
+                boolean success = topObject.get("success").getAsBoolean();
+                if (success) {
+                    return true;
+                }
+
+                if (topObject.has("error-codes")) {
+                    final List<String> errorCodes = new ArrayList<>();
+                    for (final JsonElement element : topObject.get("error-codes").getAsJsonArray()) {
+                        final String errorCode = element.getAsString();
+                        errorCodes.add(errorCode);
+                    }
+                    LOGGER.debug(pwmRequest, "recaptcha error codes: " + JsonUtil.serializeCollection(errorCodes));
+                }
             }
 
-            if (splitResponse.length > 1) {
-                final String errorCode = splitResponse[1];
-                LOGGER.debug(pwmRequest, "reCaptcha error response: " + errorCode);
-            }
         } catch (Exception e) {
             final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage();
             LOGGER.error(errorMsg,e);

+ 5 - 0
pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java

@@ -153,6 +153,11 @@ public class ConfigEditorServlet extends PwmServlet {
             configManagerBean.setConfiguration(loadedConfig);
         }
 
+        pwmSession.setSessionTimeout(
+                pwmRequest.getHttpServletRequest().getSession(),
+                Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_EDITOR_IDLE_TIMEOUT)));
+
+
         final ConfigEditorAction action = readProcessAction(pwmRequest);
 
         if (action != null) {

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

@@ -509,10 +509,14 @@ public class ConfigGuideServlet extends PwmServlet {
             final ContextManager contextManager = ContextManager.getContextManager(pwmRequest);
             try {
                 writeConfig(contextManager, configGuideBean);
-            } catch (PwmOperationalException e) {
+            } catch (PwmException e) {
                 final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
                 pwmRequest.outputJsonResult(restResultBean);
                 return;
+            } catch (Exception e) {
+                final RestResultBean restResultBean = RestResultBean.fromError(new ErrorInformation(PwmError.ERROR_UNKNOWN,"error during save: " + e.getMessage()), pwmRequest);
+                pwmRequest.outputJsonResult(restResultBean);
+                return;
             }
             final HashMap<String,String> resultData = new HashMap<>();
             resultData.put("serverRestart","true");

+ 3 - 3
pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java

@@ -239,7 +239,6 @@ public class ForgottenPasswordServlet extends PwmServlet {
 
         if (forgottenPasswordBean.getUserInfo() == null) {
             pwmRequest.sendRedirectToContinue();
-            return;
         }
     }
 
@@ -310,7 +309,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
         final List<FormConfiguration> forgottenPasswordForm = pwmApplication.getConfig().readSettingAsForm(
                 PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM);
 
-        Map<FormConfiguration, String> formValues = new HashMap();
+        Map<FormConfiguration, String> formValues = new HashMap<>();
 
         try {
             //read the values from the request
@@ -1102,7 +1101,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                         userInfoBean.getUserIdentity(),
                         theUser
                 );
-                challengeSet = responseSet == null ? null : responseSet.getChallengeSet();
+                challengeSet = responseSet == null ? null : responseSet.getPresentableChallengeSet();
             } catch (ChaiValidationException e) {
                 final String errorMsg = "unable to determine presentable challengeSet for stored responses: " + e.getMessage();
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NO_CHALLENGES, errorMsg);
@@ -1319,6 +1318,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
             break;
 
             case CHALLENGE_RESPONSES: {
+                pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordChallengeSet, forgottenPasswordBean.getPresentableChallengeSet());
                 pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_RESPONSES);
             }
             break;

+ 30 - 4
pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java

@@ -84,7 +84,6 @@ import java.util.*;
 public class HelpdeskServlet extends PwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskServlet.class);
-    private static final String TOKEN_NAME = HelpdeskServlet.class.getName();
 
     public enum HelpdeskAction implements PwmServlet.ProcessAction {
         doUnlock(HttpMethod.POST),
@@ -286,7 +285,7 @@ public class HelpdeskServlet extends PwmServlet {
 
         // check user identity matches helpdesk bean user
         final UserInfoBean detailUserInfo = helpdeskBean.getHeldpdeskDetailInfo().getUserInfoBean();
-        if (helpdeskBean == null || detailUserInfo == null || !userIdentity.equals(detailUserInfo.getUserIdentity())) {
+        if (detailUserInfo == null || !userIdentity.equals(detailUserInfo.getUserIdentity())) {
             pwmRequest.setResponseError(new ErrorInformation(PwmError.ERROR_UNKNOWN,"requested user for delete  is not currently selected user"));
             pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
             return;
@@ -369,7 +368,7 @@ public class HelpdeskServlet extends PwmServlet {
             final String errorMsg = "cannot select self";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
             LOGGER.debug(pwmRequest, errorInformation);
-            pwmRequest.respondWithError(errorInformation);
+            pwmRequest.respondWithError(errorInformation, false);
             return;
         }
 
@@ -411,7 +410,8 @@ public class HelpdeskServlet extends PwmServlet {
         searchConfiguration.setEnableContextValidation(false);
         searchConfiguration.setUsername(username);
         searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setFilter(helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_SEARCH_FILTER));
+        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig(),helpdeskProfile));
+
         if (!useProxy) {
             final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity();
             searchConfiguration.setLdapProfile(loggedInUser.getLdapProfileID());
@@ -805,6 +805,32 @@ public class HelpdeskServlet extends PwmServlet {
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_DETAIL);
     }
 
+    private static String getSearchFilter(final Configuration configuration, final HelpdeskProfile helpdeskProfile) {
+        final String configuredFilter = helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_SEARCH_FILTER);
+        if (configuredFilter != null && !configuredFilter.isEmpty()) {
+            return configuredFilter;
+        }
+
+        final List<String> defaultObjectClasses = configuration.readSettingAsStringArray(PwmSetting.DEFAULT_OBJECT_CLASSES);
+        final List<FormConfiguration> searchAttributes = helpdeskProfile.readSettingAsForm(PwmSetting.HELPDESK_SEARCH_FORM);
+        final StringBuilder filter = new StringBuilder();
+        filter.append("(&"); //open AND clause for objectclasses and attributes
+        for (final String objectClass : defaultObjectClasses) {
+            filter.append("(objectClass=").append(objectClass).append(")");
+        }
+        filter.append("(|"); // open OR clause for attributes
+        for (final FormConfiguration formConfiguration : searchAttributes) {
+            if (formConfiguration != null && formConfiguration.getName() != null) {
+                final String searchAttribute = formConfiguration.getName();
+                filter.append("(").append(searchAttribute).append("=*").append(PwmConstants.VALUE_REPLACEMENT_USERNAME).append("*)");
+            }
+        }
+        filter.append(")"); // close OR clause
+        filter.append(")"); // close AND clause
+        return filter.toString();
+    }
+
+
     private static ChaiUser getChaiUser(
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,

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

@@ -145,7 +145,6 @@ public class NewUserServlet extends PwmServlet {
         final NewUserBean newUserBean = pwmSession.getNewUserBean();
 
         if (action != null) {
-            pwmRequest.validatePwmFormID();
             switch (action) {
                 case checkProgress:
                     restCheckProgress(pwmRequest, newUserBean);
@@ -322,6 +321,7 @@ public class NewUserServlet extends PwmServlet {
             pwmRequest.outputJsonResult(restResultBean);
         } catch (PwmOperationalException e) {
             final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
+            LOGGER.error(pwmRequest, "error while validating new user form: " + e.getMessage());
             pwmRequest.outputJsonResult(restResultBean);
         }
     }
@@ -406,12 +406,14 @@ public class NewUserServlet extends PwmServlet {
                     newUserBean.setNewUserForm(newUserFormFromToken);
                     newUserBean.setFormPassed(true);
                     newUserBean.setEmailTokenPassed(true);
+                    newUserBean.setEmailTokenIssued(true);
                     newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                     tokenPassed = true;
                 } else if (NewUserBean.NewUserVerificationPhase.SMS.getTokenName().equals(tokenPayload.getName())) {
                     if (newUserBean.getNewUserForm() != null && newUserBean.getNewUserForm().isConsistentWith(newUserFormFromToken)) {
                         LOGGER.debug(pwmRequest, "SMS token passed");
                         newUserBean.setSmsTokenPassed(true);
+                        newUserBean.setSmsTokenIssued(true);
                         newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                         tokenPassed = true;
                     } else {

+ 106 - 38
pwm/servlet/src/password/pwm/http/servlet/OAuthConsumerServlet.java

@@ -32,16 +32,15 @@ import org.apache.http.util.EntityUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmException;
-import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.error.*;
 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.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.util.*;
@@ -59,6 +58,14 @@ import java.util.Map;
 
 public class OAuthConsumerServlet extends PwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(OAuthConsumerServlet.class);
+    private static int oauthStateIDcounter = 0;
+
+
+    private static class OAuthState implements Serializable {
+        private final int stateID = oauthStateIDcounter++;
+        private String sessionID;
+        private String nextUrl;
+    }
 
     @Override
     protected ProcessAction readProcessAction(PwmRequest request)
@@ -72,15 +79,24 @@ public class OAuthConsumerServlet extends PwmServlet {
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final Configuration config = pwmApplication.getConfig();
+        final Configuration config = pwmRequest.getConfig();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final Settings settings = Settings.fromConfiguration(pwmApplication.getConfig());
 
-        if (!pwmSession.getSessionStateBean().isOauthInProgress()) {
+        final boolean userIsAuthenticated = pwmSession.getSessionStateBean().isAuthenticated();
+
+        if (!userIsAuthenticated && !pwmSession.getSessionStateBean().isOauthInProgress()) {
+            final String requestStateStr = pwmRequest.readParameterAsString(config.readAppProperty(AppProperty.HTTP_PARAM_OAUTH_STATE));
+            if (requestStateStr != null && requestStateStr.length() > 0) {
+                final String nextUrl = parseNextUrlFromState(requestStateStr, pwmRequest, false);
+                LOGGER.debug(pwmSession, "received unrecognized oauth response, ignoring authcode and redirecting to embedded next url: " + nextUrl);
+                pwmRequest.sendRedirect(nextUrl);
+                return;
+            }
             final String errorMsg = "oauth consumer reached, but oauth authentication has not yet been initiated.";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
-            LOGGER.error(pwmSession, errorMsg);
             pwmRequest.respondWithError(errorInformation);
+            LOGGER.error(pwmSession, errorMsg);
             return;
         }
 
@@ -88,32 +104,30 @@ public class OAuthConsumerServlet extends PwmServlet {
         if (oauthRequestError != null && !oauthRequestError.isEmpty()) {
             final String errorMsg = "error detected from oauth request parameter: " + oauthRequestError;
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg,"Remote Error: " + oauthRequestError,null);
-            LOGGER.error(pwmSession, errorMsg);
+            LOGGER.error(pwmSession,errorMsg);
             pwmRequest.respondWithError(errorInformation);
             return;
         }
 
-        // mark the inprogress flag to false, if we read this far and fail user needs to start over.
+        if (userIsAuthenticated) {
+            LOGGER.debug(pwmSession, "oauth consumer reached, but user is already authenticated; will proceed and verify authcode matches current user identity.");
+        }
+
+        // mark the inprogress flag to false, if we get this far and fail user needs to start over.
         pwmSession.getSessionStateBean().setOauthInProgress(false);
 
-        final String requestStateStr = pwmRequest.readParameterAsString(
-                config.readAppProperty(AppProperty.HTTP_PARAM_OAUTH_STATE));
+        final String requestStateStr = pwmRequest.readParameterAsString(config.readAppProperty(AppProperty.HTTP_PARAM_OAUTH_STATE));
         if (requestStateStr == null || requestStateStr.isEmpty()) {
             final String errorMsg = "state parameter is missing from oauth request";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
             LOGGER.error(pwmSession,errorMsg);
             pwmRequest.respondWithError(errorInformation);
             return;
-        } else if (!requestStateStr.equals(pwmSession.getSessionStateBean().getSessionVerificationKey())) {
-            final String errorMsg = "state value does not match current session key value";
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
-            LOGGER.error(pwmSession,errorMsg);
-            pwmRequest.respondWithError(errorInformation);
-            return;
         }
 
+        final String nextUrl = parseNextUrlFromState(requestStateStr, pwmRequest, true);
         final String requestCodeStr = pwmRequest.readParameterAsString(config.readAppProperty(AppProperty.HTTP_PARAM_OAUTH_CODE));
-        LOGGER.trace(pwmRequest, "received code from oauth server: " + requestCodeStr);
+        LOGGER.trace(pwmSession,"received code from oauth server: " + requestCodeStr);
 
         final OAuthResolveResults resolveResults;
         {
@@ -150,34 +164,44 @@ public class OAuthConsumerServlet extends PwmServlet {
 
         LOGGER.debug(pwmSession, "received user login id value from OAuth server: " + oauthSuppliedUsername);
 
-        try {
-            final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(pwmApplication, pwmSession);
-            sessionAuthenticator.authUserWithUnknownPassword(oauthSuppliedUsername, AuthenticationType.AUTH_WITHOUT_PASSWORD);
-
-            if (resolveResults.getExpiresSeconds() > 0) {
-                final LoginInfoBean loginInfoBean = pwmRequest.getPwmSession().getLoginInfoBean();
+        if (userIsAuthenticated) {
+            try {
+                final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
+                final UserIdentity resolvedIdentity = userSearchEngine.resolveUsername(oauthSuppliedUsername, null, null);
+                if (resolvedIdentity != null && resolvedIdentity.equals(pwmSession.getUserInfoBean().getUserIdentity())) {
+                    LOGGER.debug(pwmSession, "verified incoming oauth code for already authenticated session does resolve to same as logged in user");
+                } else {
+                    final String errorMsg = "incoming oauth code for already authenticated session does not resolve to same as logged in user ";
+                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
+                    LOGGER.error(pwmSession,errorMsg);
+                    pwmRequest.respondWithError(errorInformation);
+                    pwmSession.unauthenticateUser();
+                    return;
+                }
+            } catch (PwmOperationalException e) {
+                final String errorMsg = "error while examining incoming oauth code for already authenticated session: " + e.getMessage();
+                final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
+                LOGGER.error(pwmSession,errorMsg);
+                pwmRequest.respondWithError(errorInformation);
+                return;
+            }
+        }
 
-                final Date accessTokenExpirationDate = new Date(System.currentTimeMillis() + 1000 * resolveResults.getExpiresSeconds());
-                LOGGER.trace(pwmRequest, "noted oauth access token expiration at timestamp " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(accessTokenExpirationDate));
-                loginInfoBean.setOauthExpiration(accessTokenExpirationDate);
-                loginInfoBean.setOauthRefreshToken(resolveResults.getRefreshToken());
+        try {
+            if (!userIsAuthenticated) {
+                final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(pwmApplication, pwmSession);
+                sessionAuthenticator.authUserWithUnknownPassword(oauthSuppliedUsername, AuthenticationType.AUTH_WITHOUT_PASSWORD);
             }
 
             // recycle the session to prevent session fixation attack.
             pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded(true);
 
             // see if there is a an original request url
-            pwmRequest.sendRedirectToPreLoginUrl();
-        } catch (ChaiUnavailableException e) {
-            final String errorMsg = "unable to reach ldap server during OAuth authentication attempt: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, errorMsg);
-            LOGGER.error(pwmSession, errorInformation);
-            pwmRequest.respondWithError(errorInformation);
-            return;
+            LOGGER.debug(pwmSession, "oauth authentication completed, redirecting to originally requested URL: " + nextUrl);
+            pwmRequest.sendRedirect(nextUrl);
         } catch (PwmException e) {
-            final String errorMsg = "error during OAuth authentication attempt: " + e.getMessage();
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);
-            LOGGER.error(pwmSession, errorInformation);
+            LOGGER.error(pwmSession, "error during OAuth authentication attempt: " + e.getMessage());
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, e.getMessage());
             pwmRequest.respondWithError(errorInformation);
             return;
         }
@@ -507,4 +531,48 @@ public class OAuthConsumerServlet extends PwmServlet {
             this.refreshToken = refreshToken;
         }
     }
+
+    public static String makeStateStringForRequest(
+            final PwmRequest pwmRequest,
+            final String nextUrl
+    )
+            throws PwmUnrecoverableException
+    {
+            OAuthState oAuthState = new OAuthState();
+            oAuthState.sessionID = pwmRequest.getPwmSession().getSessionStateBean().getSessionVerificationKey();
+            oAuthState.nextUrl = nextUrl;
+
+            LOGGER.trace(pwmRequest, "issuing oauth state id="
+                    + oAuthState.stateID + " with the next destination URL set to " + oAuthState.nextUrl);
+
+
+
+            final String jsonValue = JsonUtil.serialize(oAuthState);
+            return SecureHelper.encryptToString(jsonValue, pwmRequest.getConfig().getSecurityKey(), true);
+    }
+
+    public static String parseNextUrlFromState(
+            final String input,
+            final PwmRequest pwmRequest,
+            final boolean validateSessionState
+    )
+            throws PwmUnrecoverableException
+    {
+            final String stateJson = SecureHelper.decryptStringValue(input, pwmRequest.getConfig().getSecurityKey(), true);
+            final OAuthState oAuthState = JsonUtil.deserialize(stateJson, OAuthState.class);
+
+            if (validateSessionState) {
+                if (oAuthState == null || !oAuthState.sessionID.equals(pwmRequest.getPwmSession().getSessionStateBean().getSessionVerificationKey())) {
+                    final String errorMsg = "state value does not match current session key value";
+                    final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR, errorMsg);
+                    LOGGER.error(pwmRequest, errorMsg);
+                    throw new PwmUnrecoverableException(errorInformation);
+                }
+            }
+
+            LOGGER.trace(pwmRequest, "while parsing oauth state id="
+                    + oAuthState.stateID + ", determined the next destination URL to be " + oAuthState.nextUrl);
+
+            return oAuthState.nextUrl;
+    }
 }

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

@@ -204,6 +204,9 @@ public class PeopleSearchServlet extends PwmServlet {
             }
         }
 
+        final int peopleSearchIdleTimeout = (int)pwmRequest.getConfig().readSettingAsLong(PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS);
+        pwmRequest.getPwmSession().setSessionTimeout(pwmRequest.getHttpServletRequest().getSession(),peopleSearchIdleTimeout);
+
         final PeopleSearchActions peopleSearchAction = this.readProcessAction(pwmRequest);
         if (peopleSearchAction != null) {
             switch (peopleSearchAction) {
@@ -334,7 +337,7 @@ public class PeopleSearchServlet extends PwmServlet {
         final List<FormConfiguration> detailFormConfig = config.readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
         final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
                 detailFormConfig, pwmSession.getSessionStateBean().getLocale());
-        final ChaiUser theUser = config.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY)
+        final ChaiUser theUser = useProxy(pwmApplication,pwmSession)
                 ? pwmApplication.getProxiedChaiUser(userIdentity)
                 : pwmSession.getSessionManager().getActor(pwmApplication, userIdentity);
         Map<String, String> values = null;
@@ -351,7 +354,8 @@ public class PeopleSearchServlet extends PwmServlet {
     private void restUserDetailRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+    {
         final Date startTime = new Date();
         final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
 
@@ -580,6 +584,7 @@ public class PeopleSearchServlet extends PwmServlet {
 
         final boolean useProxy = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY);
         final boolean publicAccessEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC);
+
         if (useProxy) {
             return true;
         }
@@ -689,6 +694,6 @@ public class PeopleSearchServlet extends PwmServlet {
 
     private static Set<String> getSearchAttributes(final Configuration configuration) {
         final List<String> searchResultForm = configuration.readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES);
-        return Collections.unmodifiableSet(new HashSet<String>(searchResultForm));
+        return Collections.unmodifiableSet(new HashSet<>(searchResultForm));
     }
 }

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

@@ -85,7 +85,7 @@ public abstract class PwmServlet extends HttpServlet {
                     final ErrorInformation errorInformation = e.getErrorInformation();
                     final PwmSession pwmSession = PwmSessionWrapper.readPwmSession(req);
                     LOGGER.error(pwmSession, errorInformation.toDebugStr());
-                    pwmRequest.respondWithError(errorInformation);
+                    pwmRequest.respondWithError(errorInformation,false);
                     return;
                 }
                 throw e;

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

@@ -283,7 +283,7 @@ public class UpdateProfileServlet extends PwmServlet {
         LOGGER.trace(sessionLabel, "loading existing user profile data from ldap");
         final Map<String,String> userData = new LinkedHashMap<>();
         try {
-            userData.putAll(userDataReader.readStringAttributes(FormConfiguration.convertToListOfNames(formFields)));
+            userData.putAll(userDataReader.readStringAttributes(FormConfiguration.convertToListOfNames(formFields), true));
         } catch (ChaiOperationException e) {
             LOGGER.error(sessionLabel, "unexpected error reading profile data attributes: " + e.getMessage());
         }

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

@@ -59,7 +59,7 @@ Button_OK=OK
 Display_ActivateUser=To confirm your identity, please enter the following information. Your information will be used to locate and activate your user account.<p/>Be sure to complete the process, or your account will not be activated properly.
 Display_AutoGeneratedPassword=Auto-generate a new password
 Display_CapsLockIsOn=CAPS LOCK is on
-Display_Captcha=Please enter the verification code below.  Typing in this code helps to protect your account from abuse.
+Display_Captcha=Please complete the verification process.  This process helps to protect your account from abuse.
 Display_CaptchaInputWords=Enter the text displayed above
 Display_CaptchaInputNumbers=Enter the numbers you hear
 Display_CaptchaGetAudio=Get an audio CAPTCHA

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

@@ -152,7 +152,7 @@ Error_RedirectIllegal=The requested redirect url is not permitted.
 Error_CryptError=An unexpected cryptography error has occurred.
 Error_SmsSendError=Unable to send sms message: %1%
 Error_LdapDataError=An LDAP data error has occurred.
-Error_MacroParseError=Macro parsing error: %1%
+Error_MacroParseError=Macro parse error: %1%
 Error_NoProfileAssigned=No profile is assigned for this operation.
 
 Error_ConfigUploadSuccess=File uploaded successfully

+ 2 - 11
pwm/servlet/src/password/pwm/ldap/UserSearchEngine.java

@@ -47,7 +47,6 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.stats.Statistic;
 
 import java.io.Serializable;
-import java.math.BigInteger;
 import java.util.*;
 
 public class UserSearchEngine {
@@ -68,15 +67,6 @@ public class UserSearchEngine {
         this(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
     }
 
-    private static String nextSearchID() {
-        searchCounter++;
-        if (searchCounter < 0) {
-            searchCounter = 0;
-        }
-
-        return BigInteger.valueOf(searchCounter).toString(16).toLowerCase();
-    }
-
     private static String figureSearchFilterForParams(
             final Map<FormConfiguration, String> formValues,
             final String searchFilter,
@@ -382,8 +372,9 @@ public class UserSearchEngine {
         searchHelper.setFilter(searchFilter);
         searchHelper.setAttributes(returnAttributes);
         searchHelper.setTimeLimit((int)timeoutMs);
+        final int searchID = searchCounter++;
 
-        final String debugInfo = "searchID=" + nextSearchID() + " profile=" + ldapProfile.getIdentifier() + " base=" + context + " filter=" + searchHelper.toString();
+        final String debugInfo = "searchID=" + searchID + " profile=" + ldapProfile.getIdentifier() + " base=" + context + " filter=" + searchHelper.toString();
         LOGGER.debug(sessionLabel, "performing ldap search for user; " + debugInfo);
 
         final Date startTime = new Date();

+ 3 - 2
pwm/servlet/src/password/pwm/ldap/auth/AuthenticationRequest.java

@@ -239,14 +239,15 @@ class AuthenticationRequest {
                 sessionLabel.getSrcHostname()
         ));
 
-        final ChaiProvider returnProvider = determineUserProvider(requestedAuthType, password);
-        return new AuthenticationResult(returnProvider, requestedAuthType, password);
+        final ChaiProvider returnProvider = determineUserProvider(returnAuthType, password);
+        return new AuthenticationResult(returnProvider, returnAuthType, password);
     }
 
     private void initialize() {
         if (startTime != null) {
             throw new IllegalStateException("AuthenticationRequest can not be used more than once");
         }
+        log(PwmLogLevel.DEBUG, "preparing to authenticate user using authenticationType=" + this.requestedAuthType + " using strategy " + this.strategy);
         startTime = new Date();
     }
 

+ 6 - 0
pwm/servlet/src/password/pwm/util/ServletHelper.java

@@ -371,6 +371,12 @@ public class ServletHelper {
         if (ssBean.getLocale() == null) {
             initializeLocaleAndTheme(pwmRequest.getHttpServletRequest(), pwmApplication, pwmSession);
         }
+
+        // set idle timeout (may get overridden by module-specific values elsewhere
+        {
+            final int sessionIdleSeconds = (int) pwmApplication.getConfig().readSettingAsLong(PwmSetting.IDLE_TIMEOUT_SECONDS);
+            pwmSession.setSessionTimeout(pwmRequest.getHttpServletRequest().getSession(), sessionIdleSeconds);
+        }
     }
 
     private static void initializeLocaleAndTheme(

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

@@ -22,8 +22,10 @@
 
 package password.pwm.util.cli;
 
-import com.novell.ldapchai.ChaiUser;
-import org.apache.log4j.*;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
 import org.apache.log4j.varia.NullAppender;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
@@ -34,12 +36,19 @@ import password.pwm.util.Helper;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBFactory;
+import password.pwm.util.logging.PwmLogLevel;
+import password.pwm.util.logging.PwmLogManager;
+import password.pwm.util.logging.PwmLogger;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.util.*;
 
 public class MainClass {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(MainClass.class);
+
+    private static final String LOGGING_PATTERN = "%d{yyyy-MM-dd HH:mm:ss}, %-5p, %c{2}, %m%n";
 
     public static final Map<String,CliCommand> COMMANDS;
     static {
@@ -99,7 +108,7 @@ public class MainClass {
             throws Exception
     {
         final Map<String,Object> options = parseCommandOptions(parameters, args);
-        final File applicationPath = new File(".").getCanonicalFile();
+        final File applicationPath = figureApplicationPath(MAIN_OPTIONS);
         final File configurationFile = locateConfigurationFile(applicationPath);
 
         final ConfigurationReader configReader = loadConfiguration(configurationFile);
@@ -109,7 +118,7 @@ public class MainClass {
                 : null;
         final LocalDB localDB = parameters.needsLocalDB
                 ? pwmApplication == null
-                ? loadPwmDB(config, parameters.readOnly)
+                ? loadPwmDB(config, parameters.readOnly, applicationPath)
                 : pwmApplication.getLocalDB()
                 : null;
 
@@ -189,10 +198,14 @@ public class MainClass {
         return returnObj;
     }
 
-    public static void main(final String[] args)
+    static MainOptions MAIN_OPTIONS = new MainOptions();
+
+    public static void main(String[] args)
             throws Exception
     {
-        initLog4j(false);
+        args = parseCommandOptions(args);
+
+        initLog4j(MAIN_OPTIONS.pwmLogLevel);
 
         final String commandStr = args == null || args.length < 1 ? null : args[0];
 
@@ -224,33 +237,84 @@ public class MainClass {
         }
     }
 
+    static String[] parseCommandOptions(String[] args) {
+        final String OPT_DEBUG_LEVEL = "-debugLevel";
+        final String OPT_APP_PATH = "-applicationPath";
+
+        if (args == null || args.length < 1) {
+            return args;
+        }
+
+        final List<String> outputArgs = new ArrayList<>();
+        for (final String arg : args) {
+            if (arg != null) {
+                if (arg.startsWith(OPT_DEBUG_LEVEL)) {
+                    if (arg.length() < OPT_DEBUG_LEVEL.length() + 2) {
+                        out(OPT_DEBUG_LEVEL + " switch must include level (example: -debugLevel=TRACE");
+                        System.exit(-1);
+                    } else {
+                        final String levelStr = arg.substring(OPT_DEBUG_LEVEL.length() + 1, arg.length());
+                        final PwmLogLevel pwmLogLevel;
+                        try {
+                            pwmLogLevel = PwmLogLevel.valueOf(levelStr.toUpperCase());
+                            MAIN_OPTIONS.pwmLogLevel = pwmLogLevel;
+                        } catch (IllegalArgumentException e) {
+                            out(" unknown log level value: " + levelStr);
+                            System.exit(-1);
+                        }
+                    }
+                } else  if (arg.startsWith(OPT_APP_PATH)) {
+                    if (arg.length() < OPT_DEBUG_LEVEL.length() + 2) {
+                        out(OPT_APP_PATH + " switch must include value (example: -debugLevel=/tmp/applicationPath");
+                        System.exit(-1);
+                    } else {
+                        final String pathStr = arg.substring(OPT_DEBUG_LEVEL.length() + 1, arg.length());
+                        final File pathValue = new File(pathStr);
+                        if (!pathValue.exists()) {
+                            exitWithError(" specified applicationPath '" + pathStr + "' does not exist");
+                        }
+                        if (!pathValue.isDirectory()) {
+                            exitWithError(" specified applicationPath '" + pathStr + "' must be a directory");
+                        }
+                        MAIN_OPTIONS.applicationPath = pathValue;
+                    }
+                } else {
+                    outputArgs.add(arg);
+                }
+            }
+        }
+        return outputArgs.toArray(new String[outputArgs.size()]);
+    }
 
-    static void initLog4j(boolean show) {
-        if (!show) {
+    static void initLog4j(PwmLogLevel logLevel) {
+        if (logLevel == null) {
             Logger.getRootLogger().removeAllAppenders();
             Logger.getRootLogger().addAppender(new NullAppender());
             return;
         }
 
-
-        // clear all existing package loggers
-        final String pwmPackageName = PwmApplication.class.getPackage().getName();
-        final Logger pwmPackageLogger = Logger.getLogger(pwmPackageName);
-        final String chaiPackageName = ChaiUser.class.getPackage().getName();
-        final Logger chaiPackageLogger = Logger.getLogger(chaiPackageName);
-        final Layout patternLayout = new PatternLayout("%d{yyyy-MM-dd HH:mm:ss}, %-5p, %c{2}, %m%n");
+        final Layout patternLayout = new PatternLayout(LOGGING_PATTERN);
         final ConsoleAppender consoleAppender = new ConsoleAppender(patternLayout);
-        final Level level = Level.toLevel(Level.INFO_INT);
-        pwmPackageLogger.addAppender(consoleAppender);
-        pwmPackageLogger.setLevel(level);
-        chaiPackageLogger.addAppender(consoleAppender);
-        chaiPackageLogger.setLevel(level);
+        for (final Package logPackage : PwmLogManager.LOGGING_PACKAGES) {
+            if (logPackage != null) {
+                final Logger logger = Logger.getLogger(logPackage.getName());
+                logger.addAppender(consoleAppender);
+                logger.setLevel(logLevel.getLog4jLevel());
+            }
+        }
+        PwmLogger.markInitialized();
     }
 
-    static LocalDB loadPwmDB(final Configuration config, final boolean readonly) throws Exception {
+    static LocalDB loadPwmDB(
+            final Configuration config,
+            final boolean readonly,
+            final File applicationPath
+    )
+            throws Exception
+    {
         final File databaseDirectory;
         final String pwmDBLocationSetting = config.readSettingAsString(PwmSetting.PWMDB_LOCATION);
-        databaseDirectory = Helper.figureFilepath(pwmDBLocationSetting, new File("."));
+        databaseDirectory = Helper.figureFilepath(pwmDBLocationSetting, applicationPath);
         return LocalDBFactory.getInstance(databaseDirectory, readonly, null, config);
     }
 
@@ -288,4 +352,26 @@ public class MainClass {
     private static void out(CharSequence txt) {
         System.out.println(txt + "\n");
     }
+
+    private static class MainOptions {
+        private PwmLogLevel pwmLogLevel = null;
+        private File applicationPath = null;
+    }
+
+    private static void exitWithError(final String msg) {
+        out(msg);
+        System.exit(-1);
+    }
+
+    private static File figureApplicationPath(final MainOptions mainOptions) throws IOException {
+        final File applicationPath;
+        if (mainOptions != null && mainOptions.applicationPath != null) {
+            applicationPath = mainOptions.applicationPath;
+        } else {
+            applicationPath = new File(".").getCanonicalFile();
+        }
+
+        LOGGER.debug("using applicationPath " + applicationPath.getAbsolutePath());
+        return applicationPath;
+    }
 }

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

@@ -298,6 +298,8 @@ public class LocalDBUtility {
                 }
             }
         } catch (Exception e) {
+            LOGGER.error("error while examining LocalDB: " + e.getMessage());
+        } finally {
             if (iter != null) {
                 iter.close();
             }

+ 4 - 3
pwm/servlet/src/password/pwm/util/logging/PwmLogManager.java

@@ -43,7 +43,7 @@ import java.util.List;
 public class PwmLogManager {
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmLogManager.class);
 
-    private static final List<Package> LOGGING_PACKAGES = Collections.unmodifiableList(Arrays.asList(
+    public static final List<Package> LOGGING_PACKAGES = Collections.unmodifiableList(Arrays.asList(
             PwmApplication.class.getPackage(),
             ChaiUser.class.getPackage(),
             Package.getPackage("org.jasig.cas.client")
@@ -54,6 +54,7 @@ public class PwmLogManager {
         for (final Package logPackage : LOGGING_PACKAGES) {
             if (logPackage != null) {
                 final Logger logger = Logger.getLogger(logPackage.getName());
+                logger.setAdditivity(false);
                 logger.removeAllAppenders();
                 logger.setLevel(Level.TRACE);
             }
@@ -72,8 +73,6 @@ public class PwmLogManager {
             final File pwmApplicationPath,
             final String fileLogLevel
     ) {
-        deinitializeLogger();
-
         PwmLogger.setPwmApplication(pwmApplication);
 
         // try to configure using the log4j config file (if it exists)
@@ -90,6 +89,8 @@ public class PwmLogManager {
             }
         }
 
+        deinitializeLogger();
+
         // if we haven't yet configured log4j for whatever reason, do so using the hardcoded defaults and level (if supplied)
         final Layout patternLayout = new PatternLayout(config.readAppProperty(AppProperty.LOGGING_PATTERN));
 

+ 8 - 2
pwm/servlet/src/password/pwm/util/logging/PwmLogger.java

@@ -50,15 +50,21 @@ public class PwmLogger {
     private static PwmLogLevel minimumDbLogLevel;
     private static PwmApplication pwmApplication;
     private static RollingFileAppender fileAppender;
+    private static boolean initialized;
 
     private final String name;
     private final org.apache.log4j.Logger log4jLogger;
     private final boolean localDBDisabled;
 
-
+    public static void markInitialized() {
+        initialized = true;
+    }
 
     static void setPwmApplication(final PwmApplication pwmApplication) {
         PwmLogger.pwmApplication = pwmApplication;
+        if (pwmApplication != null) {
+            initialized = true;
+        }
     }
 
     static void setLocalDBLogger(final PwmLogLevel minimumDbLogLevel, final LocalDBLogger localDBLogger) {
@@ -175,7 +181,7 @@ public class PwmLogger {
         final Throwable throwable = logEvent.getThrowable();
         final PwmLogLevel level = logEvent.getLevel();
 
-        if (pwmApplication != null) {
+        if (initialized) {
             switch (level) {
                 case DEBUG:
                     log4jLogger.debug(wrappedMessage, throwable);

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

@@ -32,6 +32,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.util.PwmRandom;
+import password.pwm.util.StringUtil;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -52,6 +53,10 @@ public abstract class StandardMacros {
     public static final List<Class<? extends MacroImplementation>> STANDARD_MACROS;
     static {
         final List<Class<? extends MacroImplementation>> defaultMacros = new ArrayList<>();
+
+        // wrapper macros
+        defaultMacros.add(EncodingMacro.class);
+
         defaultMacros.add(LdapMacro.class);
         defaultMacros.add(UserPwExpirationTimeMacro.class);
         defaultMacros.add(UserPwExpirationTimeDefaultMacro.class);
@@ -520,4 +525,74 @@ public abstract class StandardMacros {
         }
     }
 
+    public static class EncodingMacro extends AbstractMacro {
+        private static final Pattern PATTERN = Pattern.compile("@Encode:[^:]+:\\[\\[.*\\]\\]@");
+        // @Encode:ENCODE_TYPE:value@
+
+        private static enum ENCODE_TYPE {
+            urlPath,
+            urlParameter,
+            base64,
+
+            ;
+
+            private String encode(final String input) throws MacroParseException {
+                switch (this) {
+                    case urlPath:
+                        return StringUtil.urlEncode(input);
+
+                    case urlParameter:
+                        return StringUtil.urlEncode(input);
+
+                    case base64:
+                        return StringUtil.base64Encode(input.getBytes(PwmConstants.DEFAULT_CHARSET));
+
+                    default:
+                        throw new MacroParseException("unimplemented encodeType '" + this.toString() + "' for Encode macro");
+                }
+            }
+
+            private static ENCODE_TYPE forString(final String input) {
+                for (final ENCODE_TYPE encodeType : ENCODE_TYPE.values()) {
+                    if (encodeType.toString().equalsIgnoreCase(input)) {
+                        return encodeType;
+                    }
+                }
+                return null;
+            }
+        }
+
+
+        public Pattern getRegExPattern() {
+            return PATTERN;
+        }
+
+        public String replaceValue(
+                final String matchValue,
+                final MacroRequestInfo macroRequestInfo
+        )
+                throws MacroParseException
+        {
+            if (matchValue == null || matchValue.length() < 1) {
+                return "";
+            }
+
+            final String[] colonParts = matchValue.split(":");
+
+            if (colonParts.length < 3) {
+                throw new MacroParseException("not enough arguments for Encode macro");
+            }
+
+            final String encodeMethodStr = colonParts[1];
+            final ENCODE_TYPE encodeType = ENCODE_TYPE.forString(encodeMethodStr);
+            if (encodeType == null) {
+                throw new MacroParseException("unknown encodeType '" + encodeMethodStr + "' for Encode macro");
+            }
+
+            String value = matchValue; // can't use colonParts[2] as it may be split if value contains a colon.
+            value = value.replaceAll("^@Encode:[^:]+:\\[\\[","");
+            value = value.replaceAll("\\]\\]@$","");
+            return encodeType.encode(value);
+        }
+    }
 }

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

@@ -132,11 +132,7 @@ public class ActionExecutor {
                 final MacroMachine macroMachine = settings.getMacroMachine();
 
                 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);
-                }
+                body = body == null ? "" : macroMachine.expandMacros(body);
 
                 for (final String headerName : headers.keySet()) {
                     final String headerValue = headers.get(headerName);
@@ -152,13 +148,13 @@ public class ActionExecutor {
             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()
-                    ));
-                }
+            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()
+                ));
+            }
 
         } catch (Exception e) {
             if (e instanceof PwmOperationalException) {

+ 11 - 0
pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java

@@ -489,6 +489,17 @@ public class PasswordUtility {
         // send email notification
         sendChangePasswordHelpdeskEmailNotice(pwmSession, pwmApplication, userInfoBean);
 
+        // expire if so configured
+        if (helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_FORCE_PW_EXPIRATION)) {
+            LOGGER.trace(pwmSession,"preparing to expire password for user " + userIdentity.toDisplayString());
+            try {
+                proxiedUser.expirePassword();
+            } catch (ChaiOperationException e) {
+                LOGGER.warn(pwmSession, "error while forcing password expiration for user " + userIdentity.toDisplayString() + ", error: " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+
         // send password
         final boolean sendPassword = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_SEND_PASSWORD);
         if (sendPassword) {

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

@@ -46,6 +46,7 @@
         <%@ include file="fragment/admin-nav.jsp" %>
         <form action="Administration" method="post" enctype="application/x-www-form-urlencoded"
               name="searchForm" id="searchForm" class="pwm-form">
+            <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
             <table style="">
                 <tr style="width:0">
                     <td class="key" style="border:0">

+ 26 - 53
pwm/servlet/web/WEB-INF/jsp/captcha.jsp

@@ -1,4 +1,3 @@
-<%@ page import="password.pwm.util.StringUtil" %>
 <%--
   ~ Password Management Servlets (PWM)
   ~ http://code.google.com/p/pwm/
@@ -22,28 +21,21 @@
   --%>
 
 <!DOCTYPE html>
-<%@ page language="java" session="true" isThreadSafe="true"
-         contentType="text/html; charset=UTF-8" %>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html; charset=UTF-8" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <%-- begin reCaptcha section (http://code.google.com/apis/recaptcha/docs/display.html) --%>
-<pwm:script-ref url="<%=(String)JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaClientUrl)%>"/>
 <pwm:script>
     <script type="text/javascript">
-        PWM_GLOBAL['startupFunctions'].push(function(){
-            Recaptcha.create("<%=StringUtil.escapeJS((String)JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaPublicKey))%>",
-                    "recaptcha_widget",
-                    {
-                        theme: "custom",
-                        lang: '<%=JspUtility.getPwmRequest(pageContext).getLocale()%>',
-                        callback: Recaptcha.focus_response_field
-                    }
-            );
-        });
+        function recaptchaCallback() {
+            console.log('captcha completed, submitting form');
+            PWM_MAIN.handleFormSubmit(PWM_MAIN.getObject('verifyCaptcha'));
+        }
     </script>
 </pwm:script>
+<pwm:script-ref url="<%=(String)JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaClientUrl)%>"/>
 <div id="wrapper">
     <jsp:include page="fragment/header-body.jsp">
         <jsp:param name="pwm.PageName" value="Title_Captcha"/>
@@ -52,48 +44,29 @@
         <p><pwm:display key="Display_Captcha"/></p>
         <%@ include file="fragment/message.jsp" %>
         <br/>
-        <form action="<pwm:url url='Captcha'/>" method="post" enctype="application/x-www-form-urlencoded" name="verifyCaptcha" class="pwm-form">
-            <div class="recaptcha_WaitDialogBlank">
-                <div id="recaptcha_widget" style="display:none" class="recaptcha_widget">
-                    <div id="recaptcha_image"></div>
-                    <div class="recaptcha_input">
-                        <label class="recaptcha_only_if_image" for="recaptcha_response_field"><pwm:display key="Display_CaptchaInputWords"/></label>
-                        <label class="recaptcha_only_if_audio" for="recaptcha_response_field"><pwm:display key="Display_CaptchaInputNumbers"/></label>
-                        <input type="text" id="recaptcha_response_field" name="recaptcha_response_field">
+        <form action="<pwm:url url='Captcha'/>" method="post" enctype="application/x-www-form-urlencoded" id="verifyCaptcha" name="verifyCaptcha" class="pwm-form">
+            <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
+            <center>
+                <div data-callback="recaptchaCallback" style="margin: auto !important" class="g-recaptcha" data-sitekey="<%=JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaPublicKey)%>"></div>
+            </center>
+            <%--
+            <noscript>
+                <div style="width: 302px; height: 352px;">
+                    <div style="width: 302px; height: 352px; position: relative;">
+                        <div style="width: 302px; height: 352px; position: absolute;">
+                            <iframe src="https://www.google.com/recaptcha/api/fallback?k=your_site_key"
+                                    frameborder="0" scrolling="no"
+                                    style="width: 302px; height:352px; border-style: none;">
+                            </iframe>
+                        </div>
+                        <div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
+                            <textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;" value="">
+                            </textarea>
+                        </div>
                     </div>
-                    <ul class="recaptcha_options">
-                        <li>
-                            <a href="javascript:Recaptcha.reload()">
-                                <span class="fa fa-refresh" title="<pwm:display key="Display_CaptchaRefresh"/>"></span>
-                            </a>
-                        </li>
-                        <li class="recaptcha_only_if_image">
-                            <a href="javascript:Recaptcha.switch_type('audio')">
-                                <span class="fa fa-volume-up" title="<pwm:display key="Display_CaptchaGetAudio"/>"></span>
-                            </a>
-                        </li>
-                        <li class="recaptcha_only_if_audio">
-                            <a href="javascript:Recaptcha.switch_type('image')">
-                                <span class="fa fa-picture" title="<pwm:display key="Display_CaptchaGetImage"/>"></span>
-                            </a>
-                        </li>
-                        <li>
-                            <a href="javascript:Recaptcha.showhelp()">
-                                <span class="fa fa-question-sign" title="<pwm:display key="Display_CaptchaHelp"/>"></span>
-                            </a>
-                        </li>
-                    </ul>
                 </div>
-            </div>
-            <noscript>
-                <iframe nonce="<pwm:value name="cspNonce"/>" src="<%=JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaIframeUrl)%>"
-                        height="300" width="500" frameborder="0"></iframe>
-                <br>
-                <textarea name="recaptcha_challenge_field" rows="3" cols="40">
-                </textarea>
-                <input type="hidden" name="recaptcha_response_field"
-                       value="manual_challenge">
             </noscript>
+            --%>
             <div class="buttonbar">
                 <input type="hidden" name="processAction" value="doVerify"/>
                 <button type="submit" name="verify" class="btn" id="verify_button">

+ 2 - 2
pwm/servlet/web/WEB-INF/jsp/changepassword-form.jsp

@@ -39,12 +39,12 @@
         <p><pwm:display key="Display_ChangePasswordForm"/></p>
         <%@ include file="fragment/message.jsp" %>
         <br/>
-        <form action="<pwm:url url='ChangePassword'/>" method="post" enctype="application/x-www-form-urlencoded" class="pwm-form" onreset="PWM_CHANGEPW.setInputFocus()" name="changePasswordForm" id="changePasswordForm">
+        <form action="<pwm:url url='ChangePassword'/>" method="post" enctype="application/x-www-form-urlencoded" class="pwm-form" name="changePasswordForm" id="changePasswordForm">
             <% if (JspUtility.getPwmSession(pageContext).getChangePasswordBean().isCurrentPasswordRequired()) { %>
             <h1>
                 <label for="currentPassword"><pwm:display key="Field_CurrentPassword"/></label>
             </h1>
-            <input id="currentPassword" type="<pwm:value name="passwordFieldType"/>" class="inputfield" name="currentPassword"/>
+            <input id="currentPassword" type="<pwm:value name="passwordFieldType"/>" class="inputfield" name="currentPassword" <pwm:autofocus/>  />
             <br/>
             <% } %>
             <jsp:include page="fragment/form.jsp"/>

+ 1 - 1
pwm/servlet/web/WEB-INF/jsp/changepassword-wait.jsp

@@ -52,7 +52,7 @@
     <div id="centerbody" >
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <p><pwm:display key="Display_PleaseWaitPassword"/></p>
-        <div style="width:80%; margin-left: 10%; margin-right: 10%; padding-top: 70px">
+        <div class="meteredProgressBar">
             <div data-dojo-type="dijit/ProgressBar" style="width:100%" data-dojo-id="passwordProgressBar" id="passwordProgressBar" data-dojo-props="maximum:100"></div>
         </div>
         <div style="text-align: center; width: 100%; padding-top: 50px">

+ 1 - 1
pwm/servlet/web/WEB-INF/jsp/configeditor.jsp

@@ -84,7 +84,7 @@
                         <input type="search" id="homeSettingSearch" name="homeSettingSearch" class="inputfield" <pwm:autofocus/>/>
                     </td>
                     <td>
-                        <div style="margin-top:5px">
+                        <div style="margin-top:5px; width:20px; max-width: 20px;">
                             <div id="indicator-searching" style="display: none">
                                 <span style="" class="fa fa-lg fa-spin fa-spinner"></span>
                             </div>

+ 30 - 105
pwm/servlet/web/WEB-INF/jsp/configguide-ldap.jsp

@@ -46,7 +46,7 @@
         </div>
     </div>
     <div id="centerbody">
-        <form id="widgetForm" name="widgetForm">
+        <form id="configForm" name="configForm">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <div id="outline_ldap-server" class="setting_outline">
                 <div id="titlePaneHeader-ldap-server" class="setting_title">
@@ -65,21 +65,7 @@
                         <tr>
                             <td colspan="2">
                                 <span class="fa fa-chevron-circle-right"></span>
-                                <input id="widget_<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" name="widget_<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_HOST)%>"/>
-                                <pwm:script>
-                                    <script type="text/javascript">
-                                        PWM_GLOBAL['startupFunctions'].push(function(){
-                                            require(["dijit/form/ValidationTextBox"],function(ValidationTextBox){
-                                                new ValidationTextBox({
-                                                    required: true,
-                                                    style: "width: 520px",
-                                                    placeholder: '<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP_HOST)%>',
-                                                    value: '<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_HOST)%>'
-                                                }, "widget_<%=ConfigGuideServlet.PARAM_LDAP_HOST%>");
-                                            });
-                                        });
-                                    </script>
-                                </pwm:script>
+                                <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" name="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_HOST)%>" <pwm:autofocus/> />
                             </td>
                         </tr>
                         <tr><td>&nbsp;</td></tr>
@@ -94,52 +80,15 @@
                         <tr>
                             <td>
                                 <span class="fa fa-chevron-circle-right"></span>
-                                <input id="widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" name="widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_PORT)%>"/>
-                                <pwm:script>
-                                    <script type="text/javascript">
-                                        PWM_GLOBAL['startupFunctions'].push(function(){
-                                            require(["dijit/registry","dijit/form/NumberSpinner"],function(registry,NumberSpinner){
-                                                new NumberSpinner({
-                                                    id: 'widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>',
-                                                    required: true,
-                                                    style: "width: 70px",
-                                                    placeholder: '<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP_PORT)%>',
-                                                    constraints:{min:1,max:65535,places:0,pattern:'#'},
-                                                    value: '<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_PORT)%>'
-                                                }, "widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>");
-                                            });
-                                        });
-                                    </script>
-                                </pwm:script>
+                                <input class="configNumericInput" type="number" id="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" name="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_PORT)%>"/>
                             </td>
                             <td>
+                                <% boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_SECURE));%>
                                 <span class="fa fa-chevron-circle-right"></span>
-                                <input id="widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" name="widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>"/>
-                                <pwm:script>
-                                    <script type="text/javascript">
-                                        PWM_GLOBAL['startupFunctions'].push(function(){
-                                            require(["dijit/registry","dijit/form/ToggleButton"],function(registry,ToggleButton){
-                                                new ToggleButton({
-                                                    iconClass:'dijitCheckBoxIcon',
-                                                    label: 'Secure',
-                                                    style: 'width:100px',
-                                                    onChange: function() {
-                                                        console.log('onchange trigger!');
-                                                        PWM_MAIN.getObject('widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>').value = this.checked ? '636' : '389';
-                                                        if (!this.checked) {
-                                                            PWM_MAIN.showConfirmDialog({text:'<pwm:display key="Confirm_SSLDisable" bundle="Config"/>', okAction:function() {
-                                                                registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').set('checked', 'true');
-                                                                handleFormActivity();
-                                                            }});
-                                                        }
-                                                        handleFormActivity();
-                                                    },
-                                                    checked: <%="true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_SECURE))%>
-                                                },'widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>');
-                                            });
-                                        });
-                                    </script>
-                                </pwm:script>
+                                <label class="checkboxWrapper">
+                                    <input type="checkbox" id="widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" name="nope" <%=secureChecked ? "checked" : ""%>/> Secure
+                                    <input type="hidden" id="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" name="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" value="uninitialized"/>
+                                </label>
                             </td>
                         </tr>
                     </table>
@@ -158,42 +107,14 @@
                         <b>Proxy/Admin LDAP DN</b>
                         <br/>
                         <span class="fa fa-chevron-circle-right"></span>
-                        <input id="widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" name="widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>"/>
-                        <pwm:script>
-                            <script type="text/javascript">
-                                PWM_GLOBAL['startupFunctions'].push(function(){
-                                    require(["dijit/form/ValidationTextBox"],function(ValidationTextBox){
-                                        new ValidationTextBox({
-                                            required: true,
-                                            style: "width: 520px",
-                                            placeholder: '<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP_ADMIN_DN)%>',
-                                            value: '<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_DN)%>'
-                                        }, "widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>");
-                                    });
-                                });
-                            </script>
-                        </pwm:script>
+                        <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>"/>
                     </div>
                     &nbsp;<br/>
                     <div class="setting_item">
                         <b>Password</b>
                         <br/>
                         <span class="fa fa-chevron-circle-right"></span>
-                        <input class="passwordfield" id="widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" name="widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" type="<pwm:value name="passwordFieldType"/>"/>
-                        <pwm:script>
-                            <script type="text/javascript">
-                                PWM_GLOBAL['startupFunctions'].push(function(){
-                                    require(["dijit/form/ValidationTextBox"],function(ValidationTextBox){
-                                        new ValidationTextBox({
-                                            required: true,
-                                            type: "password",
-                                            style: "width: 200px",
-                                            value: '<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_PW)%>'
-                                        }, "widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>");
-                                    });
-                                });
-                            </script>
-                        </pwm:script>
+                        <input class="configStringInput" type="password" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" />
                     </div>
                 </div>
             </div>
@@ -221,22 +142,12 @@
     </div>
     <div class="push"></div>
 </div>
-<form id="configForm" name="configForm">
-    <input type="hidden" name="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" id="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>"/>
-    <input type="hidden" name="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" id="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>"/>
-    <input type="hidden" name="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" id="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>"/>
-    <input type="hidden" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>"/>
-    <input type="hidden" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>"/>
-</form>
 <pwm:script>
     <script type="text/javascript">
         function handleFormActivity() {
             require(["dijit/registry"],function(registry){
-                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_HOST%>').value = registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_HOST%>').get('value');
-                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_PORT%>').value = registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_PORT%>').get('value');
-                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').value = registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').checked;
-                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>').value = registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>').get('value');
-                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>').value = registry.byId('widget_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>').get('value');
+                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').value =
+                        PWM_MAIN.getObject('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').checked ? "true" : "false";
                 PWM_GUIDE.updateForm();
                 clearHealthDiv();
             });
@@ -248,17 +159,31 @@
 
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_VAR['originalHealthBody'] = PWM_MAIN.getObject('healthBody').innerHTML;
-            require(["dojo/parser","dijit/TitlePane","dijit/form/Form","dijit/form/ValidationTextBox","dijit/form/NumberSpinner","dijit/form/CheckBox"],function(dojoParser){
                 clearHealthDiv();
-            });
             checkIfNextEnabled();
 
+            PWM_MAIN.addEventHandler('configForm','input',function(){
+                handleFormActivity();
+            });
+
+            PWM_MAIN.addEventHandler('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>','change',function() {
+                if (!PWM_MAIN.getObject('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').checked) {
+                    PWM_MAIN.showConfirmDialog({
+                        text: PWM_CONFIG.showString('Confirm_SSLDisable'), cancelAction: function () {
+                            PWM_MAIN.getObject('widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>').checked=true;
+                            PWM_MAIN.closeWaitDialog();
+                            handleFormActivity();
+
+                        }
+                    });
+                }
+                handleFormActivity();
+            });
+
             PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
             PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
 
             PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
-            PWM_MAIN.addEventHandler('widgetForm','input',function(){handleFormActivity()});
-
         });
 
         function checkIfNextEnabled() {

+ 4 - 5
pwm/servlet/web/WEB-INF/jsp/forgottenpassword-responses.jsp

@@ -21,12 +21,11 @@
   --%>
 
 <%@ page import="com.novell.ldapchai.cr.Challenge" %>
-<%@ page import="password.pwm.http.bean.ForgottenPasswordBean" %>
+<%@ page import="com.novell.ldapchai.cr.ChallengeSet" %>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html; charset=UTF-8" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<% final PwmRequest pwmRequest = PwmRequest.forRequest(request, response); %>
-<% final ForgottenPasswordBean recoverBean = pwmRequest.getPwmSession().getForgottenPasswordBean(); %>
+<% final ChallengeSet challengeSet = (ChallengeSet)JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordChallengeSet); %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <%--
@@ -47,7 +46,7 @@ this is handled this way so on browsers where hiding fields is not possible, the
 
             <% // loop through challenges
                 int counter = 0;
-                for (final Challenge loopChallenge : recoverBean.getPresentableChallengeSet().getChallenges()) {
+                for (final Challenge loopChallenge : challengeSet.getChallenges()) {
                     counter++;
             %>
             <h2><label for="PwmResponse_R_<%=counter%>"><%= loopChallenge.getChallengeText() %>
@@ -59,7 +58,7 @@ this is handled this way so on browsers where hiding fields is not possible, the
                 <input type="hidden" name="processAction" value="checkResponses"/>
                 <button type="submit" name="checkResponses" class="btn" id="submitBtn">
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
-                    <pwm:display key="Button_RecoverPassword"/>
+                                                   <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" >

+ 1 - 1
pwm/servlet/web/WEB-INF/jsp/fragment/form.jsp

@@ -42,7 +42,7 @@
 <% if (formConfigurationList == null) { %>
 [ form definition is not available ]
 <% } else if (formConfigurationList.isEmpty()) { %>
-[ form containes no items ]
+<!-- form contains no items ] -->
 <% } else { %>
 <%
     final boolean forceReadOnly = (Boolean)JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.FormReadOnly);

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

@@ -37,7 +37,7 @@
               class="pwm-form">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <h2><label for="password"><pwm:display key="Field_Password"/></label></h2>
-            <input type="<pwm:value name="passwordFieldType"/>" name="password" id="password" class="inputfield passwordfield" autofocus/>
+            <input type="<pwm:value name="passwordFieldType"/>" name="password" id="password" class="inputfield passwordfield" <pwm:autofocus/> />
             <div class="buttonbar">
                 <button type="submit" class="btn" name="button" id="submitBtn">
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-sign-in"></span></pwm:if>

+ 2 - 3
pwm/servlet/web/WEB-INF/jsp/newuser-wait.jsp

@@ -48,9 +48,8 @@
     <div id="centerbody">
         <p><pwm:display key="Display_PleaseWaitNewUser"/></p>
         <%@ include file="fragment/message.jsp" %>
-
-        <div style="width:400px; margin-left: auto; margin-right: auto; padding-top: 70px">
-            <div data-dojo-type="dijit/ProgressBar" style="width:400px" data-dojo-id="createProgressBar" id="createProgressBar" data-dojo-props="maximum:100"></div>
+        <div class="meteredProgressBar">
+            <div data-dojo-type="dijit/ProgressBar" style="width:100%" data-dojo-id="createProgressBar" id="createProgressBar" data-dojo-props="maximum:100"></div>
         </div>
         <div style="text-align: center; width: 100%; padding-top: 50px">
             <%--

+ 3 - 6
pwm/servlet/web/WEB-INF/jsp/newuser.jsp

@@ -22,13 +22,10 @@
   --%>
 
 <!DOCTYPE html>
-
-<%@ page language="java" session="true" isThreadSafe="true"
-         contentType="text/html; charset=UTF-8" %>
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.ALWAYS_EXPAND_MESSAGE_TEXT); %>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html; charset=UTF-8" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<%
-    final PwmRequest pwmRequest = PwmRequest.forRequest(request,response);
-%>
+<% final PwmRequest pwmRequest = PwmRequest.forRequest(request,response); %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">

+ 12 - 11
pwm/servlet/web/WEB-INF/jsp/peoplesearch.jsp

@@ -57,21 +57,22 @@
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
 
         <div id="searchControlPanel" style="position: relative; margin-left: auto; margin-right: auto; width: 100%; text-align: center;">
-            <br/>
-            <table class="noborder" style="margin-left: auto; margin-right: auto; width:100px; table-layout: fixed" >
+            <table class="noborder" style="margin-left: auto; margin-right: auto; width:100px;" >
                 <tr>
-                    <td style="width:15px">
+                    <td style="width:5%">
                         <span class="fa fa-search"></span>
                     </td>
-                    <td style="width:400px">
+                    <td style="width:90%">
                         <input type="search" id="username" name="username" class="peoplesearch-input-username" <pwm:autofocus/> autocomplete="off"/>
                     </td>
-                    <td style="width:20px">
-                        <div id="searchIndicator" style="display: none">
-                            <span style="" class="fa fa-lg fa-spin fa-spinner"></span>
-                        </div>
-                        <div id="maxResultsIndicator" style="display: none;">
-                            <span style="color: #ffcd59;" class="fa fa-lg fa-exclamation-circle"></span>
+                    <td style="width:5%">
+                        <div style="width:20px; max-width: 20px">
+                            <div id="searchIndicator" style="display: none">
+                                <span style="" class="fa fa-lg fa-spin fa-spinner"></span>
+                            </div>
+                            <div id="maxResultsIndicator" style="display: none;">
+                                <span style="color: #ffcd59;" class="fa fa-lg fa-exclamation-circle"></span>
+                            </div>
                         </div>
                     </td>
                 </tr>
@@ -80,8 +81,8 @@
                 <span><pwm:display key="Display_JavascriptRequired"/></span>
                 <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
             </noscript>
-            <br/>
         </div>
+        <br/>
         <div id="peoplesearch-searchResultsGrid" class="grid">
         </div>
     </div>

+ 1 - 11
pwm/servlet/web/WEB-INF/web.xml

@@ -179,17 +179,7 @@
     </filter-mapping>
     <filter-mapping>
         <filter-name>SessionFilter</filter-name>
-        <url-pattern>/public/NewUser</url-pattern>
-        <url-pattern>/public/ActivateUser</url-pattern>
-        <url-pattern>/public/ForgottenPassword</url-pattern>
-        <url-pattern>/public/ForgottenUsername</url-pattern>
-        <url-pattern>/public/CommandServlet</url-pattern>
-        <url-pattern>/public/oauth</url-pattern>
-        <url-pattern>/public/Logout</url-pattern>
-        <url-pattern>/private/*</url-pattern>
-        <url-pattern>/config/*</url-pattern>
-        <url-pattern>/</url-pattern>
-        <url-pattern>*.jsp</url-pattern>
+        <url-pattern>*</url-pattern>
     </filter-mapping>
     <filter-mapping>
         <filter-name>CaptchaFilter</filter-name>

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

@@ -35,7 +35,7 @@
 %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="../WEB-INF/jsp/fragment/header.jsp" %>
-<body class="nihilo">
+<body>
 <div id="wrapper">
     <jsp:include page="../WEB-INF/jsp/fragment/header-body.jsp">
         <jsp:param name="pwm.PageName" value="Title_MainPage"/>

+ 2 - 1
pwm/servlet/web/public/reference/license.jsp

@@ -27,6 +27,8 @@
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_HEADER_WARNINGS); %>
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_THEME); %>
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_HEADER_BUTTONS); %>
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.NO_REQ_COUNTER); %>
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_FOOTER_TEXT); %>
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
 <body class="nihilo">
@@ -272,7 +274,6 @@
     });
 </script>
 </pwm:script>
-<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_FOOTER_TEXT); %>
 <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %>
 </body>
 </html>

+ 2 - 1
pwm/servlet/web/public/resources/configStyle.css

@@ -156,7 +156,8 @@
 
 .btn {
     font-family: Trebuchet MS, sans-serif;
-    border-radius: 3px;
+    padding: 2px 9px;
+    border-radius: 4px;
     background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #3e374c), color-stop(1, #08070f) );
     background:linear-gradient(to bottom, #3e374c 5%, #08070f 100% );
 }.btn:hover {

+ 6 - 0
pwm/servlet/web/public/resources/js/admin.js

@@ -88,6 +88,12 @@ PWM_ADMIN.initAdminOtherMenu=function() {
                     PWM_MAIN.goto(PWM_GLOBAL['url-context'] + '/private/config/ConfigManager');
                 }
             }));
+            pMenu.addChild(new MenuItem({
+                label: 'Configuration Editor',
+                onClick: function() {
+                    PWM_CONFIG.startConfigurationEditor();
+                }
+            }));
 
 
             var dropDownButton = new DropDownButton({

+ 248 - 239
pwm/servlet/web/public/resources/js/configeditor-settings.js

@@ -1319,7 +1319,6 @@ ActionHandler.defaultValue = {
     ldapMethod:"replace",
     url:"",
     body:"",
-    bodyEncoding:"url",
     headers:{},
     attributeName:"",
     attributeValue:""
@@ -1335,10 +1334,6 @@ 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);
@@ -1517,16 +1512,6 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
             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') {
@@ -1566,10 +1551,6 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
                         PWM_VAR['clientSettingCache'][keyName][iteration]['method'] = value;
                         ActionHandler.writeFormSetting(keyName, function(){ ActionHandler.showOptionsDialog(keyName,iteration)});
                     });
-                    PWM_MAIN.addEventHandler('select-' + inputID + '-bodyEncoding','change',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(){
                         PWM_VAR['clientSettingCache'][keyName][iteration]['url'] = PWM_MAIN.getObject('input-' + inputID + '-url').value;
                         ActionHandler.writeFormSetting(keyName);
@@ -1680,6 +1661,13 @@ ActionHandler.addHeader = function(keyName, iteration) {
 // -------------------------- email table handler ------------------------------------
 
 var EmailTableHandler = {};
+EmailTableHandler.defaultValue = {
+    to:"@User:Email@",
+    from:"@DefaultEmailFromAddress@",
+    subject:"Subject",
+    bodyPlain:"Body",
+    bodyHtml:"Body"
+};
 
 EmailTableHandler.init = function(keyName) {
     console.log('EmailTableHandler init for ' + keyName);
@@ -1689,161 +1677,134 @@ EmailTableHandler.init = function(keyName) {
     });
 };
 
-EmailTableHandler.draw = function(keyName) {
-    var resultValue = PWM_VAR['clientSettingCache'][keyName];
-    var parentDiv = 'table_setting_' + keyName;
+EmailTableHandler.draw = function(settingKey) {
+    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
+    var parentDiv = 'table_setting_' + settingKey;
     PWM_CFGEDIT.clearDivElements(parentDiv, true);
-    require(["dojo/parser","dojo/html","dijit/form/ValidationTextBox","dijit/form/Textarea"],
-        function(dojoParser,dojoHtml,ValidationTextBox,Textarea){
-            PWM_CFGEDIT.clearDivElements(parentDiv, false);
-            for (var localeName in resultValue) {
-                EmailTableHandler.drawRow(keyName,localeName,parentDiv)
-            }
+    PWM_CFGEDIT.clearDivElements(parentDiv, false);
 
-            if (PWM_MAIN.isEmpty(resultValue)) {
-                var newTableRow = document.createElement("tr");
-                newTableRow.setAttribute("style", "border-width: 0");
-                newTableRow.setAttribute("colspan", "5");
+    var htmlBody = '';
+    for (var localeName in resultValue) {
+        htmlBody += EmailTableHandler.drawRowHtml(settingKey,localeName)
+    }
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+    parentDivElement.innerHTML = htmlBody;
 
-                var newTableData = document.createElement("td");
-                newTableData.setAttribute("style", "border-width: 0;");
+    for (var localeName in resultValue) {
+        EmailTableHandler.instrumentRow(settingKey,localeName)
+    }
 
-                var addItemButton = document.createElement("button");
-                addItemButton.setAttribute("type", "[button");
-                addItemButton.setAttribute("onclick", "PWM_CFGEDIT.resetSetting('" + keyName + "',function(){PWM_CFGEDIT.loadMainPageBody()});");
-                addItemButton.setAttribute("class", "btn");
-                addItemButton.innerHTML = '<span class="btn-icon fa fa-plus-square"></span>Add Value';
-                newTableData.appendChild(addItemButton);
+    if (PWM_MAIN.isEmpty(resultValue)) {
+        var htmlBody = '<button class="btn" id="button-addValue-' + settingKey + '">';
+        htmlBody += '<span class="btn-icon fa fa-plus-square"></span>Add Value';
+        htmlBody += '</button>';
 
-                newTableRow.appendChild(newTableData);
-                var parentDivElement = PWM_MAIN.getObject(parentDiv);
-                parentDivElement.appendChild(newTableRow);
-            } else {
-                var addLocaleFunction = function(localeValue) {
-                    if (!PWM_VAR['clientSettingCache'][keyName][localeValue]) {
-                        PWM_VAR['clientSettingCache'][keyName][localeValue] = {};
-                        EmailTableHandler.writeSetting(keyName,true);
-                    }
-                };
-                UILibrary.addAddLocaleButtonRow(parentDiv, keyName, addLocaleFunction, Object.keys(PWM_VAR['clientSettingCache'][keyName]));
-            }
-            dojoParser.parse(parentDiv);
+        var parentDivElement = PWM_MAIN.getObject(parentDiv);
+        parentDivElement.innerHTML = htmlBody;
+
+        PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){
+            PWM_CFGEDIT.resetSetting(settingKey,function(){PWM_CFGEDIT.loadMainPageBody()});
         });
-};
 
-EmailTableHandler.drawRow = function(keyName, localeName, parentDiv) {
-    require(["dojo/parser","dojo/html","dijit/form/ValidationTextBox","dijit/form/Textarea"],
-        function(dojoParser,dojoHtml,ValidationTextBox,Textarea){
-            var localeTableRow = document.createElement("tr");
-            localeTableRow.setAttribute("style", "border-width: 0;");
+    } else {
+        var addLocaleFunction = function(localeValue) {
+            if (!PWM_VAR['clientSettingCache'][settingKey][localeValue]) {
+                PWM_VAR['clientSettingCache'][settingKey][localeValue] = EmailTableHandler.defaultValue;
+                EmailTableHandler.writeSetting(settingKey,true);
+            }
+        };
+        UILibrary.addAddLocaleButtonRow(parentDiv, settingKey, addLocaleFunction, Object.keys(PWM_VAR['clientSettingCache'][settingKey]));
+    }
+};
 
-            var localeTdName = document.createElement("td");
-            localeTdName.setAttribute("style", "border-width: 0; width:15px");
-            localeTdName.innerHTML = localeName;
-            localeTableRow.appendChild(localeTdName);
+EmailTableHandler.drawRowHtml = function(settingKey, localeName) {
+    var localeLabel = localeName == '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")";
+    var idPrefix = "setting-" + localeName + "-" + settingKey;
+    var htmlBody = '';
+    htmlBody += '<table style="border:0"><tr ><td style="border:0">';
+    htmlBody += '<table>';
+    if (PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) {
+        htmlBody += '<tr><td colspan="5" class="title" style="font-size:100%; font-weight:normal">' + localeLabel + '</td></tr>';
+    }
+    var outputFunction = function (labelText, typeText) {
+        htmlBody += '<tr><td style="text-align:right; border-width:0;">' + labelText + '</td>';
+        htmlBody += '<td id="button-' + typeText + '-' + idPrefix + '" style="border-width:0; width: 15px"><span class="fa fa-edit"/></ta>';
+        htmlBody += '<td style=""><div class="configStringPanel" id="panel-' + typeText + '-' + idPrefix + '"></div></td>';
+        htmlBody += '</tr>';
+    };
+    outputFunction('To', 'to');
+    outputFunction('From', 'from');
+    outputFunction('Subject', 'subject');
+    outputFunction('Plain Body', 'bodyPlain');
+    outputFunction('HTML Body', 'bodyHtml');
+
+    htmlBody += '</table></td><td style="width:20px; border:0; vertical-align:top">';
+    if (localeName != '' || PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x
+        htmlBody += '<div id="button-deleteRow-' + idPrefix + '" style="vertical-align:top" class="delete-row-icon action-icon fa fa-times"></div>';
+    }
+    htmlBody += '</td></tr></table><br/>';
+    return htmlBody;
+};
 
-            var localeTdContent = document.createElement("td");
-            localeTdContent.setAttribute("style", "border-width: 0; width: 520px");
-            localeTableRow.appendChild(localeTdContent);
 
-            var localeTableElement = document.createElement("table");
-            localeTableElement.setAttribute("style", "border-width: 0px; width:515px; margin:0");
-            localeTdContent.appendChild(localeTableElement);
+EmailTableHandler.instrumentRow = function(settingKey, localeName) {
+    var settingData = PWM_SETTINGS['settings'][settingKey];
+    var idPrefix = "setting-" + localeName + "-" + settingKey;
 
-            var idPrefix = "setting_" + localeName + "_" + keyName;
-            var htmlBody = '';
-            htmlBody += '<table class="noborder">';
-            htmlBody += '<tr><td style="width:30px; text-align:right">To</td>';
-            htmlBody += '<td><input id="' + idPrefix + '_to"/></td></tr>';
-            htmlBody += '<tr><td style="width:30px; text-align:right">From</td>';
-            htmlBody += '<td><input id="' + idPrefix + '_from"/></td></tr>';
-            htmlBody += '<tr><td style="width:30px; text-align:right">Subject</td>';
-            htmlBody += '<td><input id="' + idPrefix + '_subject"/></td></tr>';
-            htmlBody += '<tr><td style="width:30px; text-align:right">Plain Body</td>';
-            htmlBody += '<td><input id="' + idPrefix + '_bodyPlain"/></td></tr>';
-            htmlBody += '<tr><td style="width:30px; text-align:right">HTML Body</td>';
-            htmlBody += '<td><div style="border:2px solid #EAEAEA; background: white; width: 446px" onclick="EmailTableHandler.popupEditor(\'' + keyName + '\',\'' + localeName + '\')">';
-            htmlBody += PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] ? PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] : "&nbsp;" ;
-            htmlBody += '</div></td></tr>';
-            htmlBody += "</table>"
-            dojoHtml.set(localeTableElement,htmlBody);
-            var parentDivElement = PWM_MAIN.getObject(parentDiv);
-            parentDivElement.appendChild(localeTableRow);
+    var editor = function(drawTextArea, type){
+        UILibrary.stringEditorDialog({
+            title:'Edit Value - ' + settingData['label'],
+            textarea:drawTextArea,
+            value:PWM_VAR['clientSettingCache'][settingKey][localeName][type],
+            completeFunction:function(value){
+                PWM_VAR['clientSettingCache'][settingKey][localeName][type] = value;
+                PWM_CFGEDIT.writeSetting(settingKey,PWM_VAR['clientSettingCache'][settingKey],function(){
+                    EmailTableHandler.init(settingKey);
+                });
+            }
+        });
+    };
 
-            PWM_MAIN.clearDijitWidget(idPrefix + "_to");
-            new ValidationTextBox({
-                value: PWM_VAR['clientSettingCache'][keyName][localeName]['to'],
-                style: 'width: 450px',
-                required: true,
-                onChange: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['to'] = this.value;EmailTableHandler.writeSetting(keyName)}
-            },idPrefix + "_to");
-
-            PWM_MAIN.clearDijitWidget(idPrefix + "_from");
-            new ValidationTextBox({
-                value: PWM_VAR['clientSettingCache'][keyName][localeName]['from'],
-                style: 'width: 450px',
-                required: true,
-                onChange: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['from'] = this.value;EmailTableHandler.writeSetting(keyName)}
-            },idPrefix + "_from");
-
-            PWM_MAIN.clearDijitWidget(idPrefix + "_subject");
-            new ValidationTextBox({
-                value: PWM_VAR['clientSettingCache'][keyName][localeName]['subject'],
-                style: 'width: 450px',
-                required: true,
-                onChange: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['subject'] = this.value;EmailTableHandler.writeSetting(keyName)}
-            },idPrefix + "_subject");
-
-            PWM_MAIN.clearDijitWidget(idPrefix + "_bodyPlain");
-            new Textarea({
-                value: PWM_VAR['clientSettingCache'][keyName][localeName]['bodyPlain'],
-                style: 'width: 450px',
-                required: true,
-                onChange: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['bodyPlain'] = this.value;EmailTableHandler.writeSetting(keyName)}
-            },idPrefix + "_bodyPlain");
+    UILibrary.addTextValueToElement('panel-to-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['to']);
+    PWM_MAIN.addEventHandler('button-to-' + idPrefix,'click',function(){ editor(false,'to'); });
+    PWM_MAIN.addEventHandler('panel-to-' + idPrefix,'click',function(){ editor(false,'to'); });
 
-            { // add a spacer row
-                var spacerTableRow = document.createElement("tr");
-                spacerTableRow.setAttribute("style", "border-width: 0");
-                parentDivElement.appendChild(spacerTableRow);
+    UILibrary.addTextValueToElement('panel-from-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['from']);
+    PWM_MAIN.addEventHandler('button-from-' + idPrefix,'click',function(){ editor(false,'from'); });
+    PWM_MAIN.addEventHandler('panel-from-' + idPrefix,'click',function(){ editor(false,'from'); });
 
-                var spacerTableData = document.createElement("td");
-                spacerTableData.setAttribute("style", "border-width: 0");
-                spacerTableData.innerHTML = "&nbsp;";
-                spacerTableRow.appendChild(spacerTableData);
-            }
+    UILibrary.addTextValueToElement('panel-subject-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['subject']);
+    PWM_MAIN.addEventHandler('button-subject-' + idPrefix,'click',function(){ editor(false,'subject'); });
+    PWM_MAIN.addEventHandler('panel-subject-' + idPrefix,'click',function(){ editor(false,'subject'); });
 
-            if (localeName != '' || PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][keyName])){ // add remove locale x
-                var imgElement2 = document.createElement("div");
-                imgElement2.setAttribute("style", "width: 10px; height: 10px;");
-                imgElement2.setAttribute("class", "delete-row-icon action-icon fa fa-times");
-                imgElement2.setAttribute("id", "button-" + keyName + "-" + localeName + "-deleteRow");
-                //imgElement2.setAttribute("onclick", "delete PWM_VAR['clientSettingCache']['" + keyName + "']['" + localeName + "'];EmailTableHandler.writeSetting('" + keyName + "',true)");
-                var tdElement = document.createElement("td");
-                tdElement.setAttribute("style", "border-width: 0; text-align: left; vertical-align: top");
+    UILibrary.addTextValueToElement('panel-bodyPlain-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyPlain']);
+    PWM_MAIN.addEventHandler('button-bodyPlain-' + idPrefix,'click',function(){ editor(true,'bodyPlain'); });
+    PWM_MAIN.addEventHandler('panel-bodyPlain-' + idPrefix,'click',function(){ editor(true,'bodyPlain'); });
 
-                localeTableRow.appendChild(tdElement);
-                tdElement.appendChild(imgElement2);
-            }
+    UILibrary.addTextValueToElement('panel-bodyHtml-' + idPrefix,PWM_VAR['clientSettingCache'][settingKey][localeName]['bodyHtml']);
+    PWM_MAIN.addEventHandler('button-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlBodyEditor(settingKey,localeName); });
+    PWM_MAIN.addEventHandler('panel-bodyHtml-' + idPrefix,'click',function(){ EmailTableHandler.htmlBodyEditor(settingKey,localeName); });
 
-            PWM_MAIN.addEventHandler("button-" + keyName + "-" + localeName + "-deleteRow","click",function(){
-                delete PWM_VAR['clientSettingCache'][keyName][localeName];
-                EmailTableHandler.writeSetting(keyName,true);
-            });
-        });
+    PWM_MAIN.addEventHandler("button-deleteRow-" + idPrefix,"click",function(){
+        PWM_MAIN.showConfirmDialog({okAction:function(){
+            delete PWM_VAR['clientSettingCache'][settingKey][localeName];
+            EmailTableHandler.writeSetting(settingKey,true);
+        }});
+    });
 };
 
 
-EmailTableHandler.popupEditor = function(keyName, localeName) {
+EmailTableHandler.htmlBodyEditor = function(keyName, localeName) {
     require(["dijit/Editor","dijit/_editor/plugins/AlwaysShowToolbar","dijit/_editor/plugins/LinkDialog","dijit/_editor/plugins/ViewSource","dijit/_editor/plugins/FontChoice","dijit/_editor/plugins/TextColor"],
         function(Editor,AlwaysShowToolbar){
             var idValue = keyName + "_" + localeName + "_htmlEditor";
-            var idValueDialog = idValue + "_Dialog";
             var bodyText = '';
-            bodyText += '<div id="' + idValue + '" style="border:2px solid #EAEAEA; min-height: 200px;"></div>';
+            bodyText += '<div id="' + idValue + '" style="border:2px solid #EAEAEA; height:300px"></div>';
             PWM_MAIN.showDialog({
                 title: "HTML Editor",
                 text: bodyText,
+                showClose:true,
+                showCancel:true,
                 dialogClass: 'wide',
                 loadFunction:function(){
                     PWM_MAIN.clearDijitWidget(idValue);
@@ -1853,27 +1814,30 @@ EmailTableHandler.popupEditor = function(keyName, localeName) {
                             {name:"dijit/_editor/plugins/LinkDialog",command:"createLink",urlRegExp:".*"},
                             "fontName","foreColor"
                         ],
-                        height: '',
+                        height: '300px',
                         value: PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'],
-                        style: 'width: 100%',
-                        onChange: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = this.get('value')},
-                        onKeyUp: function(){PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = this.get('value')}
+                        style: '',
+                        onChange: function(){PWM_VAR['temp-dialogInputValue'] = this.get('value')},
+                        onKeyUp: function(){PWM_VAR['temp-dialogInputValue'] = this.get('value')}
                     },idValue).startup();
                 },
                 okAction:function(){
+                    PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = PWM_VAR['temp-dialogInputValue'];
                     EmailTableHandler.writeSetting(keyName,true);
                 }
             });
-        });
+        }
+    );
 };
 
 
 EmailTableHandler.writeSetting = function(settingKey, redraw) {
     var currentValues = PWM_VAR['clientSettingCache'][settingKey];
-    PWM_CFGEDIT.writeSetting(settingKey, currentValues);
-    if (redraw) {
-        EmailTableHandler.draw(settingKey);
-    }
+    PWM_CFGEDIT.writeSetting(settingKey, currentValues, function(){
+        if (redraw) {
+            EmailTableHandler.init(settingKey);
+        }
+    });
 };
 
 // -------------------------- boolean handler ------------------------------------
@@ -1907,74 +1871,91 @@ BooleanHandler.toggle = function(keyName,widget) {
 // -------------------------- challenge handler ------------------------------------
 
 var ChallengeSettingHandler = {};
-ChallengeSettingHandler.defaultItem = {text:'Question',minLength:4,maxLength:200,adminDefined:true};
+ChallengeSettingHandler.defaultItem = {text:'Question',minLength:4,maxLength:200,adminDefined:true,enforceWordlist:true,maxQuestionCharsInAnswer:3};
 
-ChallengeSettingHandler.init = function(keyName) {
-    var parentDiv = "table_setting_" + keyName;
-    console.log('ChallengeSettingHandler init for ' + keyName);
+ChallengeSettingHandler.init = function(settingKey) {
+    var parentDiv = "table_setting_" + settingKey;
+    console.log('ChallengeSettingHandler init for ' + settingKey);
     PWM_CFGEDIT.clearDivElements(parentDiv, true);
-    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
-        PWM_VAR['clientSettingCache'][keyName] = resultValue;
-        ChallengeSettingHandler.draw(keyName);
+    PWM_CFGEDIT.readSetting(settingKey, function(resultValue) {
+        PWM_VAR['clientSettingCache'][settingKey] = resultValue;
+        if (PWM_MAIN.isEmpty(resultValue)) {
+            var htmlBody = '<button class="btn" id="button-addValue-' + settingKey + '">';
+            htmlBody += '<span class="btn-icon fa fa-plus-square"></span>Add Value';
+            htmlBody += '</button>';
+
+            var parentDivElement = PWM_MAIN.getObject(parentDiv);
+            parentDivElement.innerHTML = htmlBody;
+
+            PWM_MAIN.addEventHandler('button-addValue-' + settingKey,'click',function(){
+                PWM_VAR['clientSettingCache'][settingKey] = {};
+                PWM_VAR['clientSettingCache'][settingKey][''] = [];
+                PWM_VAR['clientSettingCache'][settingKey][''].push(ChallengeSettingHandler.defaultItem);
+                ChallengeSettingHandler.write(settingKey,function(){
+                    ChallengeSettingHandler.init(settingKey);
+                });
+            });
+        } else {
+            ChallengeSettingHandler.draw(settingKey);
+        }
     });
 };
 
-ChallengeSettingHandler.draw = function(keyName) {
-    var parentDiv = "table_setting_" + keyName;
-    var resultValue = PWM_VAR['clientSettingCache'][keyName];
+ChallengeSettingHandler.draw = function(settingKey) {
+    var parentDiv = "table_setting_" + settingKey;
+    var resultValue = PWM_VAR['clientSettingCache'][settingKey];
     var parentDivElement = PWM_MAIN.getObject(parentDiv);
     var bodyText = '';
-    bodyText += '<table style="cursor: pointer; table-layout: fixed" class="noborder">';
-    bodyText += '<col style="width:60px"/>';
-    bodyText += '<col style="width:100%"/>';
     PWM_CFGEDIT.clearDivElements(parentDiv, false);
     for (var localeName in resultValue) {
         (function(localeKey) {
-            var isDefaultLocale = localeKey == "";
             var multiValues = resultValue[localeKey];
             var rowCount = PWM_MAIN.itemCount(multiValues);
-            var editJsText = 'ChallengeSettingHandler.editLocale(\'' + keyName + '\',\'' + localeKey + '\')';
+            var editJsText = 'ChallengeSettingHandler.editLocale(\'' + settingKey + '\',\'' + localeKey + '\')';
 
-            bodyText += '<tr><td style="" onclick="' + editJsText + '">';
-            bodyText += isDefaultLocale ? "Default" : localeKey;
-            bodyText += '</td>';
+            bodyText += '<table class="noborder"><tr><td>';
+            bodyText += '<table style="cursor: pointer; table-layout: fixed">';
+            var localeLabel = localeName == '' ? 'Default Locale' : PWM_GLOBAL['localeInfo'][localeName] + " (" + localeName + ")";
+            if (PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]) > 1) {
+                bodyText += '<tr><td class="title" style="font-size:100%; font-weight:normal">' + localeLabel + '</td></tr>';
+            }
 
-            bodyText += '<td onclick="' + editJsText + '"> ';
+            bodyText += '<tr>';
+            bodyText += '<td style="width:100%" onclick="' + editJsText + '"> ';
             if (rowCount > 0) {
                 for (var iteration in multiValues) {
-                    var id = 'panel-value-' + keyName + '-' + localeKey + '-' + iteration;
+                    var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
                     bodyText += '<div style="text-overflow:ellipsis; white-space:nowrap; overflow:hidden" id="' + id + '">text</div>';
                 }
             } else {
                 bodyText += '[No Questions]';
             }
             bodyText += '</td></tr>';
-            bodyText += '<tr><td>&nbsp;</td></tr>';
 
-            parentDivElement.innerHTML = bodyText;
-
-            PWM_MAIN.addEventHandler('button-' + keyName + '-' + localeKey + '-deleteRow','click',function(){
-                ChallengeSettingHandler.deleteLocale(keyName)
-            });
+            bodyText += '</table></td><td style="width:20px; border:0; vertical-align:top">';
+            if (localeName != '' || PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][settingKey]) < 2) { // add remove locale x
+                bodyText += '<div id="button-deleteRow-' + settingKey + '-' + localeKey + '" style="vertical-align:top" class="delete-row-icon action-icon fa fa-times"></div>';
+            }
+            bodyText += '</td></tr></table><br/>';
         }(localeName));
     }
-    bodyText += '</table>';
+    parentDivElement.innerHTML = bodyText;
 
     var addLocaleFunction = function(localeValue) {
-        if (localeValue in PWM_VAR['clientSettingCache'][keyName]) {
+        if (localeValue in PWM_VAR['clientSettingCache'][settingKey]) {
             PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:'Locale <i>' + localeValue + '</i> is already present.'});
         } else {
-            PWM_VAR['clientSettingCache'][keyName][localeValue] = [];
-            PWM_VAR['clientSettingCache'][keyName][localeValue][0] = ChallengeSettingHandler.defaultItem;
-            ChallengeSettingHandler.write(keyName, function(){
-                ChallengeSettingHandler.init(keyName);
+            PWM_VAR['clientSettingCache'][settingKey][localeValue] = [];
+            PWM_VAR['clientSettingCache'][settingKey][localeValue][0] = ChallengeSettingHandler.defaultItem;
+            ChallengeSettingHandler.write(settingKey, function(){
+                ChallengeSettingHandler.init(settingKey);
             });
-            //ChallengeSettingHandler.editLocale(keyName,localeValue);
         }
     };
-    var tableElement = document.createElement("table");
+    var tableElement = document.createElement("div");
     parentDivElement.appendChild(tableElement);
-    UILibrary.addAddLocaleButtonRow(tableElement, keyName, addLocaleFunction, Object.keys(resultValue));
+
+    UILibrary.addAddLocaleButtonRow(tableElement, settingKey, addLocaleFunction, Object.keys(resultValue));
 
     for (var localeName in resultValue) {
         (function(localeKey) {
@@ -1983,7 +1964,7 @@ ChallengeSettingHandler.draw = function(keyName) {
             if (rowCount > 0) {
                 for (var iteration in multiValues) {
                     (function (rowKey) {
-                        var id = 'panel-value-' + keyName + '-' + localeKey + '-' + iteration;
+                        var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
                         var questionText = multiValues[rowKey]['text'];
                         var adminDefined = multiValues[rowKey]['adminDefined'];
                         var output = (adminDefined ? questionText : '[User Defined]');
@@ -1991,6 +1972,10 @@ ChallengeSettingHandler.draw = function(keyName) {
                     }(iteration));
                 }
             }
+
+            PWM_MAIN.addEventHandler('button-deleteRow-' + settingKey + '-' + localeKey,'click',function(){
+                ChallengeSettingHandler.deleteLocale(settingKey, localeKey)
+            });
         }(localeName));
     }
 
@@ -2005,43 +1990,28 @@ ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
     require(["dojo","dijit/registry","dojo/parser","dijit/form/Button","dijit/form/ValidationTextBox","dijit/form/Textarea","dijit/form/NumberSpinner","dijit/form/ToggleButton"],
         function(dojo,registry,dojoParser){
-            var multiValues = resultValue[localeName];
 
-            dialogBody += '<table class="noborder">';
+
+            var multiValues = resultValue[localeName];
 
             for (var iteration in multiValues) {
                 (function(rowKey) {
-                    var isAdminDefined = multiValues[rowKey]['adminDefined'];
-                    var questionText = multiValues[rowKey]['text'];
-
-                    dialogBody += '<tr>';
+                    dialogBody += '<table style="border:0">';
+                    dialogBody += '<tr><td>';
+                    dialogBody += '<table class="noborder" style="margin:0"><tr>';
                     dialogBody += '<td colspan="200" style="border-width: 0;">';
 
                     var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
                     PWM_MAIN.clearDijitWidget(inputID);
 
-                    dialogBody += '<textarea id="' + inputID + '" style="width: 700px" required="required"';
-                    dialogBody += ' data-dojo-type="dijit/form/Textarea' + '"';
-                    dialogBody += ' onchange="PWM_VAR[\'clientSettingCache\'][\'' + keyName + '\'][\'' + localeKey + '\'][\'' + rowKey + '\'][\'text\'] = this.value"';
-                    if (!isAdminDefined) {
-                        dialogBody += ' disabled="disabled"';
-                        dialogBody += ' value="[User Defined]"';
-                    } else {
-                        dialogBody += ' value="' + questionText + '"';
-                    }
-                    dialogBody += '></textarea>';
-
-                    dialogBody += '<div class="delete-row-icon action-icon fa fa-times"';
-                    dialogBody += ' onclick="ChallengeSettingHandler.deleteRow(\'' + keyName + '\',\'' + localeKey + '\',\'' + rowKey + '\')");';
-                    dialogBody += '/>';
+                    dialogBody += '<input class="configStringInput" id="' + inputID + '" style="width: 700px" required="required" disabled value="Loading"/>';
 
                     dialogBody += '</td>';
                     dialogBody += '</tr>';
 
                     dialogBody += '<tr style="padding-bottom: 15px; border:0"><td style="padding-bottom: 15px; border:0">';
-                    dialogBody += '<button data-dojo-type="dijit/form/ToggleButton" data-dojo-props="iconClass:\'dijitCheckBoxIcon\',showLabel:true,label:\'Admin Defined\',checked:' + isAdminDefined + '"';
-                    dialogBody += ' onchange="ChallengeSettingHandler.toggleAdminDefinedRow(this,\'' + inputID + '\',\'' + keyName + '\',\'' + localeKey + '\',\'' + rowKey + '\')"';
-                    dialogBody += '></button>';
+
+                    dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-adminDefined-' + inputID + '" disabled/>Admin Defined</label>';
 
                     dialogBody += '</td><td style="padding-bottom: 15px; border:0">';
                     dialogBody += '<input style="width: 50px" data-dojo-type="dijit/form/NumberSpinner" value="' +multiValues[rowKey]['minLength'] + '" data-dojo-props="constraints:{min:0,max:255,places:0}""';
@@ -2056,36 +2026,74 @@ ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
                     dialogBody += ' onchange="PWM_VAR[\'clientSettingCache\'][\'' + keyName + '\'][\'' + localeKey + '\'][\'' + rowKey + '\'][\'maxQuestionCharsInAnswer\'] = this.value"/><br/> Max Question Chars';
 
                     dialogBody += '</td><td style="padding-bottom: 15px; border:0">';
-                    var enforceWordlist = multiValues[rowKey]['enforceWordlist'];
-                    dialogBody += '<button data-dojo-type="dijit/form/ToggleButton" data-dojo-props="iconClass:\'dijitCheckBoxIcon\',showLabel:true,label:\'Enforce Wordlist\',checked:' + enforceWordlist + '"';
-                    dialogBody += ' onchange="PWM_VAR[\'clientSettingCache\'][\'' + keyName + '\'][\'' + localeKey + '\'][\'' + rowKey + '\'][\'enforceWordlist\'] = this.checked"';
-                    dialogBody += '></button>';
-
-                    /*
-                     dialogBody += '</td><td style="padding-bottom: 15px; border:0">';
-                     dialogBody += '<input style="width: 50px" data-dojo-type="dijit/form/NumberSpinner" value="' +multiValues[rowKey]['points'] + '" data-dojo-props="constraints:{min:0,max:255,places:0}""';
-                     dialogBody += ' onchange="PWM_VAR['clientSettingCache'][\'' + keyName + '\'][\'' + localeKey + '\'][\'' + rowKey + '\'][\'points\'] = this.value"/><br/>Points';
-                     */
+                    dialogBody += '<label class="checkboxWrapper"><input type="checkbox" id="value-wordlist-' + inputID + '" disabled/>Wordlist</label>';
+
                     dialogBody += '</td></tr>';
+                    dialogBody += '</table></td><td style="border:0; vertical-align: top">';
+                    if (PWM_MAIN.itemCount(PWM_VAR['clientSettingCache'][keyName][localeKey]) > 1) { // add remove locale x
+
+                        dialogBody += '<div class="delete-row-icon action-icon fa fa-times" id="button-deleteRow-' + inputID + '"/>';
+                    }
+
+                    dialogBody += '</td></tr></table>';
+                    dialogBody += '<br/>';
+
                 }(iteration));
             }
 
-            dialogBody += '</table></div>';
+
+            dialogBody += '</div>';
             dialogBody += '<br/><br/><button type="button" data-dojo-type="dijit/form/Button"';
             dialogBody += ' onclick="ChallengeSettingHandler.addRow(\'' + keyName + '\',\'' + localeKey + '\')"';
             dialogBody += '><span class="btn-icon fa fa-plus-square"></span>Add Value</button>';
 
-            if (localeKey != "") {
-                dialogBody += '<button type="button" data-dojo-type="dijit/form/Button"';
-                dialogBody += ' onclick="ChallengeSettingHandler.deleteLocale(\'' + keyName + '\',\'' + localeKey + '\')"';
-                dialogBody += '><span class="btn-icon fa fa-times"></span>Delete Locale ' + localeDisplay + '</button>';
-            }
-
             var dialogTitle = PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + localeDisplay;
             PWM_MAIN.showDialog({title:dialogTitle,text:dialogBody,showClose:true,dialogClass:'wide',loadFunction:function(){
                 dojoParser.parse(PWM_MAIN.getObject('challengeLocaleDialogDiv'));
+                for (var iteration in multiValues) {
+                    (function(rowKey) {
+                        var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
+
+                        // question text
+                        var processQuestion = function() {
+                            var isAdminDefined = multiValues[rowKey]['adminDefined'];
+                            PWM_MAIN.getObject(inputID).value = isAdminDefined ? multiValues[rowKey]['text'] : '[User Defined]';
+                            PWM_MAIN.getObject(inputID).disabled = !isAdminDefined;
+                        };
+                        processQuestion();
+                        PWM_MAIN.addEventHandler(inputID, 'input', function () {
+                            //if (!multiValues[rowKey]['adminDefined']) {
+                                PWM_VAR['clientSettingCache'][keyName][localeKey][rowKey]['text'] = PWM_MAIN.getObject(inputID).value;
+                            //}
+                        });
+
+                        // admin defined checkbox
+                        PWM_MAIN.getObject('value-adminDefined-' + inputID).disabled = false;
+                        PWM_MAIN.getObject('value-adminDefined-' + inputID).checked = !multiValues[rowKey]['adminDefined'];
+                        PWM_MAIN.addEventHandler('value-adminDefined-' + inputID,'change',function(){
+                            var checked = PWM_MAIN.getObject('value-adminDefined-' + inputID).checked;
+                            multiValues[rowKey]['adminDefined'] = !checked;
+                            processQuestion();
+                        });
+
+                        // wordlist checkbox
+                        PWM_MAIN.getObject('value-wordlist-' + inputID).disabled = false;
+                        PWM_MAIN.getObject('value-wordlist-' + inputID).checked = multiValues[rowKey]['enforceWordlist'];
+                        PWM_MAIN.addEventHandler('value-wordlist-' + inputID,'change',function(){
+                            var checked = PWM_MAIN.getObject('value-wordlist-' + inputID).checked;
+                            multiValues[rowKey]['enforceWordlist'] = checked;
+                        });
+
+                        // delete row
+                        PWM_MAIN.addEventHandler('button-deleteRow-' + inputID, 'click', function () {
+                            ChallengeSettingHandler.deleteRow(keyName, localeKey, rowKey);
+                        });
+
+                    }(iteration));
+                }
+
             },okAction:function(){
-                ChallengeSettingHandler.write(keyName );
+                ChallengeSettingHandler.write(keyName);
                 ChallengeSettingHandler.draw(keyName);
             }});
         }
@@ -2097,12 +2105,13 @@ ChallengeSettingHandler.deleteLocale = function(keyName,localeKey) {
     PWM_MAIN.showConfirmDialog({
         text: 'Are you sure you want to remove all the questions for the <i>' + localeKey + '</i> locale?',
         okAction:function(){
-            PWM_MAIN.showWaitDialog();
-            delete PWM_VAR['clientSettingCache'][keyName][localeKey];
-            PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName],function(){
-                PWM_MAIN.closeWaitDialog();
-                ChallengeSettingHandler.init(keyName);
-            });
+            PWM_MAIN.showWaitDialog({loadFunction:function(){
+                delete PWM_VAR['clientSettingCache'][keyName][localeKey];
+                PWM_CFGEDIT.writeSetting(keyName, PWM_VAR['clientSettingCache'][keyName],function(){
+                    PWM_MAIN.closeWaitDialog();
+                    ChallengeSettingHandler.init(keyName);
+                });
+            }});
         }
     });
 };

+ 14 - 18
pwm/servlet/web/public/resources/js/configguide.js

@@ -28,23 +28,16 @@ var PWM_GLOBAL = PWM_GLOBAL || {};
 
 PWM_GUIDE.selectTemplate = function(template) {
     PWM_MAIN.showWaitDialog({title:'Loading...',loadFunction:function() {
-        require(["dojo"], function (dojo) {
-            dojo.xhrGet({
-                url: "ConfigGuide?processAction=selectTemplate&pwmFormID=" + PWM_GLOBAL['pwmFormID'] + "&template=" + template,
-                preventCache: true,
-                error: function (errorObj) {
-                    PWM_MAIN.showError("error starting configuration editor: " + errorObj);
-                },
-                load: function (result) {
-                    if (!result['error']) {
-                        PWM_MAIN.getObject('button_next').disabled = template == "NOTSELECTED";
-                        PWM_MAIN.closeWaitDialog();
-                    } else {
-                        PWM_MAIN.showError(result['errorDetail']);
-                    }
-                }
-            });
-        });
+        var url = "ConfigGuide?processAction=selectTemplate&template=" + template;
+        PWM_MAIN.showDialog(url,function(result){
+            if (!result['error']) {
+                PWM_MAIN.getObject('button_next').disabled = template == "NOTSELECTED";
+                PWM_MAIN.closeWaitDialog();
+            } else {
+                PWM_MAIN.showError(result['errorDetail']);
+            }
+
+        },{method:'GET'});
     }});
 };
 
@@ -76,7 +69,10 @@ PWM_GUIDE.gotoStep = function(step) {
         PWM_MAIN.preloadAll(function(){
             var url =  "ConfigGuide?processAction=gotoStep&step=" + step;
             var loadFunction = function(result) {
-                if (result['data']) {
+                if (result['error']) {
+                    PWM_MAIN.showErrorDialog(result);
+                    return;
+                } else if (result['data']) {
                     if (result['data']['serverRestart']) {
                         PWM_CONFIG.waitForRestart();
                         return;

+ 4 - 2
pwm/servlet/web/public/resources/js/configmanager.js

@@ -169,11 +169,13 @@ PWM_CONFIG.showHeaderHealth = function() {
                     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()
@@ -383,12 +385,12 @@ PWM_CONFIG.initConfigHeader = function() {
     // header initialization
     if (PWM_MAIN.getObject('header_configManagerButton')) {
         PWM_MAIN.addEventHandler('header_configManagerButton', 'click', function () {
-            PWM_MAIN.goto('/private/config/ConfigManager')
+            PWM_MAIN.goto('/private/config/ConfigManager');
         });
     }
     if (PWM_MAIN.getObject('header_configEditorButton')) {
         PWM_MAIN.addEventHandler('header_configEditorButton', 'click', function () {
-            PWM_CONFIG.startConfigurationEditor()
+            PWM_CONFIG.startConfigurationEditor();
         });
     }
     PWM_MAIN.addEventHandler('header_openLogViewerButton', 'click', function () {

+ 2 - 3
pwm/servlet/web/public/resources/js/main.js

@@ -1270,8 +1270,7 @@ PWM_MAIN.preloadImages = function(imgArray){
 
 
 PWM_MAIN.isEmpty = function(o) {
-    for (var key in o) if (o.hasOwnProperty(key)) return false;
-    return true;
+    return PWM_MAIN.itemCount(o) < 1;
 };
 
 PWM_MAIN.itemCount = function(o) {
@@ -1728,7 +1727,7 @@ PWM_MAIN.ajaxRequest = function(url,loadFunction,options) {
     };
     var preventCache = 'preventCache' in options ? options['preventCache'] : true;
     var addPwmFormID = 'addPwmFormID' in options ? options['addPwmFormID'] : true;
-    var ajaxTimeout = options['ajaxTimeout'] ? options['ajaxTimeout'] : PWM_GLOBAL['client.ajaxTypingTimeout'];
+    var ajaxTimeout = options['ajaxTimeout'] ? options['ajaxTimeout'] : PWM_MAIN.ajaxTimeout;
     var requestHeaders = {};
     requestHeaders['Accept'] = "application/json";
     requestHeaders['X-RestClientKey'] = PWM_GLOBAL['restClientKey'];

+ 2 - 2
pwm/servlet/web/public/resources/js/peoplesearch.js

@@ -174,13 +174,13 @@ PWM_PS.showUserDetail = function(userKey) {
     };
     PWM_MAIN.showWaitDialog({
         loadFunction:function(){
+            PWM_VAR['detailInProgress'] = false;
             var url = "PeopleSearch?processAction=detail";
             var loadFunction = function(data) {
-                PWM_VAR['detailInProgress'] = false;
                 if (data['error'] == true) {
                     console.error('unable to load people detail, error: ' + data['errorDetail']);
-                    PWM_MAIN.showError(data['errorDetail']);
                     PWM_MAIN.closeWaitDialog();
+                    PWM_MAIN.showErrorDialog(data);
                     return;
                 }
                 var htmlBody = PWM_PS.convertDetailResultToHtml(data['data']);

+ 30 - 4
pwm/servlet/web/public/resources/mobileStyle.css

@@ -32,8 +32,7 @@ html, body {
 #centerbody {
     width: auto;
     min-width: inherit;
-    padding-left: 3px;
-    padding-right: 3px;
+    padding: 0
 }
 
 #centerbody.wide {
@@ -45,7 +44,7 @@ html, body {
     width: 100%;
     margin: 0;
     position:relative;
-    padding:2px;
+    padding:0;
 }
 
 
@@ -72,4 +71,31 @@ html, body {
 .dialogBody {
     width: 100%;
     max-width: 100%;
-}
+}
+
+progress:not([value]) {
+    width: 90%;
+    max-width: 90%;
+    height: 20px;
+}
+
+progress .wait {
+    color: purple;
+    width: 70%;
+}
+
+.menubutton {
+    max-width: 100px;
+    width: auto;
+    padding: 2px 7px;
+    display: block;
+    border-radius: 2px;
+    border: 5px;
+    margin: 0 5px;
+}
+
+.menubutton_key {
+    border: 0;
+    width: auto;
+    table-layout: fixed;
+}

+ 9 - 135
pwm/servlet/web/public/resources/style.css

@@ -475,140 +475,6 @@ div.progress-container > div {
     font-family: monospace;
 }
 
-/* recaptcha section */
-.recaptcha_WaitDialogBlank {
-    background: url("wait.gif") no-repeat center center;
-    min-height: 150px;
-    min-width: 46px;
-    margin-left: auto;
-    margin-right: auto;
-}
-
-.recaptcha_widget {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    max-width: 300px;
-    margin-right: auto;
-    margin-left: auto;
-    border: 4px solid #000000;
-    -webkit-border-radius: 5px;
-    -moz-border-radius: 5px;
-    -ms-border-radius: 5px;
-    -o-border-radius: 5px;
-    border-radius: 5px;
-    background: #000000;
-}
-
-#recaptcha_image {
-    width: 100% !important;
-    height: auto !important
-}
-
-#recaptcha_image img {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    width: 100%;
-    height: auto;
-    background: #FFFFFF;
-    -webkit-border-radius: 2px;
-    -moz-border-radius: 2px;
-    -ms-border-radius: 2px;
-    -o-border-radius: 2px;
-    border-radius: 2px;
-    border: 3px solid #FFFFFF;
-}
-
-.recaptcha_is_showing_audio embed {
-    height: 0;
-    width: 0;
-    overflow: hidden
-}
-
-.recaptcha_is_showing_audio #recaptcha_image {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    width: 100%;
-    height: 60px;
-    background: #FFFFFF;
-    -webkit-border-radius: 2px;
-    -moz-border-radius: 2px;
-    -ms-border-radius: 2px;
-    -o-border-radius: 2px;
-    border-radius: 2px;
-    border: 3px solid #FFFFFF;
-}
-
-.recaptcha_is_showing_audio #recaptcha_image br {
-    display: none;
-}
-
-.recaptcha_is_showing_audio #recaptcha_image #recaptcha_audio_download {
-    display: block;
-}
-
-.recaptcha_input {
-    background: #DBDBDB;
-    margin: 4px 0 0;
-    padding: 0 4px 4px;
-    border: 4px solid #DBDBDB;
-    -webkit-border-radius: 2px;
-    -moz-border-radius: 2px;
-    -ms-border-radius: 2px;
-    -o-border-radius: 2px;
-    border-radius: 2px;
-}
-
-.recaptcha_input label {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    color: #000000;
-    font-size: 13px;
-    margin: 0 0 6px;
-}
-
-.recaptcha_input input {
-    -moz-box-sizing: border-box;
-    -webkit-box-sizing: border-box;
-    box-sizing: border-box;
-    width: 100%;
-    margin: 4px 0 0;
-}
-
-.recaptcha_options {
-    list-style: none;
-    margin: 4px 0 4px;
-    padding-left: 4px;
-    height: 16px
-}
-
-.recaptcha_options li {
-    float: left;
-    margin: 0 4px 0 0
-}
-
-.recaptcha_options li a {
-    text-decoration: none;
-    text-shadow: 0 1px 1px #000000;
-    font-size: 16px;
-    color: #FFFFFF;
-    display: block;
-    width: 20px;
-    height: 16px
-}
-
-.recaptcha_options li a:active {
-    position: relative;
-    top: 1px;
-    text-shadow: none
-}
-
-.captcha_hide {
-    display: none
-}
 
 /* hide recaptcha iframe near footer */
 body > iframe[src="about:blank"] {
@@ -776,6 +642,7 @@ dialog {
     animation: fadein 0.3s;
     -moz-animation: fadein 0.3s; /* Firefox */
     -webkit-animation: fadein 0.3s; /* Safari and Chrome */
+    max-width: 100%;
 }
 
 dialog .titleBar {
@@ -813,4 +680,11 @@ progress:not([value]) {
 .peopleSearch-userDetails {
     max-height: 450px;
     overflow-y: auto;
-}
+}
+
+.meteredProgressBar {
+    width:80%;
+    margin-left: 10%;
+    margin-right: 10%;
+    padding-top: 70px;
+}

+ 10 - 1
pwm/servlet/web/public/resources/text/macroHelp.html

@@ -211,7 +211,16 @@
         </td>
         <td>
             Random characters, where <span class="documentationParameter">length</span> is the number of random characters to generate and <span class="documentationParameter">characters</span> is the list of characters to be used as random characters.
-            characters..
+        </td>
+    </tr>
+    <tr>
+        <td class="key">
+            <span style="white-space: pre">@Encode:<span class="documentationParameter">type</span>:[[<span class="documentationParameter">value</span>]]@</span>
+        </td>
+        <td>
+            Encode a value using the specified type of encoding, where <span class="documentationParameter">type</span> is the type of encoding and where <span class="documentationParameter">value</span> is
+            the value to encode.  The value may include other macros.  Types permitted are <span class="documentationParameter">urlPath</span>,
+            <span class="documentationParameter">urlParameter</span> and <span class="documentationParameter">base64</span>.
         </td>
     </tr>
 </table>