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_WARNING_HEADER_SHOW                      ("client.warningHeader.show"),
     CLIENT_PW_SHOW_REVERT_TIMEOUT                   ("client.pwShowRevertTimeout"),
     CLIENT_PW_SHOW_REVERT_TIMEOUT                   ("client.pwShowRevertTimeout"),
     CLIENT_JSP_SHOW_ICONS                           ("client.jsp.showIcons"),
     CLIENT_JSP_SHOW_ICONS                           ("client.jsp.showIcons"),
-    CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT           ("configEditor.queryFilter.testLimit"),
     CONFIG_MAX_JDBC_JAR_SIZE                        ("config.maxJdbcJarSize"),
     CONFIG_MAX_JDBC_JAR_SIZE                        ("config.maxJdbcJarSize"),
     CONFIG_RELOAD_ON_CHANGE                         ("config.reloadOnChange"),
     CONFIG_RELOAD_ON_CHANGE                         ("config.reloadOnChange"),
     CONFIG_MAX_PERSISTENT_LOGIN_SECONDS             ("config.maxPersistentLoginSeconds"),
     CONFIG_MAX_PERSISTENT_LOGIN_SECONDS             ("config.maxPersistentLoginSeconds"),
     CONFIG_FILE_SCAN_FREQUENCY                      ("config.fileScanFrequencyMS"),
     CONFIG_FILE_SCAN_FREQUENCY                      ("config.fileScanFrequencyMS"),
     CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS         ("config.newuser.passwordPolicyCacheMS"),
     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"),
     FORM_EMAIL_REGEX                                ("form.email.regexTest"),
     HTTP_RESOURCES_MAX_CACHE_ITEMS                  ("http.resources.maxCacheItems"),
     HTTP_RESOURCES_MAX_CACHE_ITEMS                  ("http.resources.maxCacheItems"),
     HTTP_RESOURCES_MAX_CACHE_BYTES                  ("http.resources.maxCacheBytes"),
     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.warningHeader.show=true
 client.pwShowRevertTimeout=45000
 client.pwShowRevertTimeout=45000
 client.jsp.showIcons=true
 client.jsp.showIcons=true
-configEditor.queryFilter.testLimit=1000
 config.reloadOnChange=true
 config.reloadOnChange=true
 config.maxJdbcJarSize=10240000
 config.maxJdbcJarSize=10240000
 config.maxPersistentLoginSeconds=3600
 config.maxPersistentLoginSeconds=3600
 config.fileScanFrequencyMS=5017
 config.fileScanFrequencyMS=5017
 config.newuser.passwordPolicyCacheMS=3600000
 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-]+)*$
 form.email.regexTest=^[_+a-zA-Z0-9-]+(\\.[_a-zA-Z0-9-]+)*@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*$
 health.minimumCheckIntervalSeconds=60
 health.minimumCheckIntervalSeconds=60
 health.certificate.warnSeconds=2592000
 health.certificate.warnSeconds=2592000
@@ -150,9 +151,9 @@ queue.syslog.maxAgeMs=86400000
 queue.syslog.maxCount=100000
 queue.syslog.maxCount=100000
 queue.maxCloseTimeoutMs=5000
 queue.maxCloseTimeoutMs=5000
 reporting.ldap.searchTimeoutMs=300000
 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.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.html.stripInlineJavascript=false
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.http.stripHeaderRegex=\\n|\\r|(?ism)%0A|%0D
 security.responses.hashIterations=100000
 security.responses.hashIterations=100000

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

@@ -149,6 +149,7 @@ public abstract class PwmConstants {
         CaptchaIframeUrl,
         CaptchaIframeUrl,
         CaptchaPublicKey,
         CaptchaPublicKey,
 
 
+        ForgottenPasswordChallengeSet,
         ForgottenPasswordOptionalPageView
         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 Type { webservice, ldap }
     public enum WebMethod { delete, get, post, put }
     public enum WebMethod { delete, get, post, put }
     public enum LdapMethod { replace, add, remove }
     public enum LdapMethod { replace, add, remove }
-    public enum BodyEncoding { none, url }
 
 
     private String name;
     private String name;
     private String description;
     private String description;
@@ -44,7 +43,6 @@ public class ActionConfiguration implements Serializable {
     private Map<String,String> headers;
     private Map<String,String> headers;
     private String url;
     private String url;
     private String body;
     private String body;
-    private BodyEncoding bodyEncoding = BodyEncoding.url;
 
 
     private LdapMethod ldapMethod = LdapMethod.replace;
     private LdapMethod ldapMethod = LdapMethod.replace;
     private String attributeName;
     private String attributeName;
@@ -82,10 +80,6 @@ public class ActionConfiguration implements Serializable {
         return body;
         return body;
     }
     }
 
 
-    public BodyEncoding getBodyEncoding() {
-        return bodyEncoding;
-    }
-
     public String getAttributeName() {
     public String getAttributeName() {
         return attributeName;
         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),
             "peopleSearch.searchBase", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH),
     PEOPLE_SEARCH_ENABLE_PUBLIC(
     PEOPLE_SEARCH_ENABLE_PUBLIC(
             "peopleSearch.enablePublic", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH),
             "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.enforcePasswordPolicy", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_RESPONSES(
     HELPDESK_CLEAR_RESPONSES(
             "helpdesk.clearResponses", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
             "helpdesk.clearResponses", PwmSettingSyntax.SELECT, PwmSettingCategory.HELPDESK_PROFILE),
+    HELPDESK_FORCE_PW_EXPIRATION(
+            "helpdesk.forcePwExpiration", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_RESPONSES_BUTTON(
     HELPDESK_CLEAR_RESPONSES_BUTTON(
             "helpdesk.clearResponses.button", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
             "helpdesk.clearResponses.button", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_PROFILE),
     HELPDESK_CLEAR_OTP_BUTTON(
     HELPDESK_CLEAR_OTP_BUTTON(
@@ -957,7 +961,7 @@ public enum PwmSetting {
     // CAS SSO
     // CAS SSO
     CAS_CLEAR_PASS_URL(
     CAS_CLEAR_PASS_URL(
             "cas.clearPassUrl", PwmSettingSyntax.STRING, PwmSettingCategory.CAS_SSO),
             "cas.clearPassUrl", PwmSettingSyntax.STRING, PwmSettingCategory.CAS_SSO),
-    
+
     // http sso
     // http sso
     SSO_AUTH_HEADER_NAME(
     SSO_AUTH_HEADER_NAME(
             "security.sso.authHeaderName", PwmSettingSyntax.STRING, PwmSettingCategory.HTTP_SSO),
             "security.sso.authHeaderName", PwmSettingSyntax.STRING, PwmSettingCategory.HTTP_SSO),
@@ -991,7 +995,7 @@ public enum PwmSetting {
             "webservice.userAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.REST_CLIENT),
             "webservice.userAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.REST_CLIENT),
 
 
 
 
-    
+
     // deprecated.
     // deprecated.
     PASSWORD_POLICY_AD_COMPLEXITY(
     PASSWORD_POLICY_AD_COMPLEXITY(
             "password.policy.ADComplexity", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY),
             "password.policy.ADComplexity", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY),
@@ -1000,8 +1004,8 @@ public enum PwmSetting {
     FORGOTTEN_PASSWORD_REQUIRE_OTP(
     FORGOTTEN_PASSWORD_REQUIRE_OTP(
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
             "recovery.require.otp", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS),
 
 
-    
-    
+
+
     ;
     ;
 
 
 // ------------------------------ STATICS ------------------------------
 // ------------------------------ STATICS ------------------------------

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

@@ -1329,16 +1329,12 @@
     <setting key="password.policy.regExMatch" level="2">
     <setting key="password.policy.regExMatch" level="2">
         <label>Required Regular Expression Matches</label>
         <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>
         <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>
     <setting key="password.policy.regExNoMatch" level="2">
     <setting key="password.policy.regExNoMatch" level="2">
         <label>Disallowed Regular Expression Matches</label>
         <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>
         <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>
     <setting key="password.policy.disallowedValues" level="1">
     <setting key="password.policy.disallowedValues" level="1">
         <label>Disallowed Values</label>
         <label>Disallowed Values</label>
@@ -1389,9 +1385,7 @@
     <setting key="password.policy.changeMessage" level="1">
     <setting key="password.policy.changeMessage" level="1">
         <label>Password Change Message</label>
         <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>
         <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>
     <setting key="password.policy.ruleText" level="2">
     <setting key="password.policy.ruleText" level="2">
         <label>Password Rule Text</label>
         <label>Password Rule Text</label>
@@ -1765,9 +1759,8 @@
     <setting key="email.adminAlert.toAddress" level="1">
     <setting key="email.adminAlert.toAddress" level="1">
         <label>System Audit Event Email Alerts</label>
         <label>System Audit Event Email Alerts</label>
         <description><![CDATA[Send an email when System Audit events occur to these email addresses.]]></description>
         <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>
     <setting key="audit.system.eventList" level="1">
     <setting key="audit.system.eventList" level="1">
         <label>System Audit Event Types</label>
         <label>System Audit Event Types</label>
@@ -1791,9 +1784,8 @@
     <setting key="audit.userEvent.toAddress" level="1">
     <setting key="audit.userEvent.toAddress" level="1">
         <label>User Audit Event Email Alerts</label>
         <label>User Audit Event Email Alerts</label>
         <description><![CDATA[Send an email User Audit events occur to these email addresses.]]></description>
         <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>
     <setting key="audit.user.eventList" level="1">
     <setting key="audit.user.eventList" level="1">
         <label>User Audit Event Types</label>
         <label>User Audit Event Types</label>
@@ -2157,62 +2149,60 @@
         <label>Random Questions</label>
         <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>
         <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>
         <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>
         </default>
     </setting>
     </setting>
     <setting key="challenge.requiredChallenges" level="1">
     <setting key="challenge.requiredChallenges" level="1">
         <label>Required Questions</label>
         <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>
         <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>
     <setting key="challenge.minRandomRequired" level="1" required="true">
     <setting key="challenge.minRandomRequired" level="1" required="true">
         <label>Minimum Random Required</label>
         <label>Minimum Random Required</label>
@@ -2252,16 +2242,12 @@
     <setting key="challenge.helpdesk.randomChallenges" level="1">
     <setting key="challenge.helpdesk.randomChallenges" level="1">
         <label>Helpdesk Random Questions</label>
         <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>
         <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>
     <setting key="challenge.helpdesk.requiredChallenges" level="1">
     <setting key="challenge.helpdesk.requiredChallenges" level="1">
         <label>Helpdesk Required Questions</label>
         <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>
         <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>
     <setting key="challenge.helpdesk.minRandomsSetup" level="1" required="true">
     <setting key="challenge.helpdesk.minRandomsSetup" level="1" required="true">
         <label>Minimum Helpdesk Random Challenges Required During Setup</label>
         <label>Minimum Helpdesk Random Challenges Required During Setup</label>
@@ -2310,7 +2296,7 @@
         <label>Verification Methods</label>
         <label>Verification Methods</label>
         <description><![CDATA[Verification Methods]]></description>
         <description><![CDATA[Verification Methods]]></description>
         <default>
         <default>
-                <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
+            <value>{"methodSettings":{"CHALLENGE_RESPONSES":{"enabledState":"required"}},"minOptionalRequired":0}</value>
         </default>
         </default>
     </setting>
     </setting>
     <setting key="recovery.enable" level="1">
     <setting key="recovery.enable" level="1">
@@ -3048,6 +3034,13 @@
             <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
             <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
         </default>
         </default>
     </setting>
     </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">
     <setting key="ldap.edirectory.enableNmas" level="1" required="true">
         <label>Enable NMAS Extensions</label>
         <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>
         <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>
         </default>
     </setting>
     </setting>
     <setting key="ldap.edirectory.readChallengeSets" level="1" required="true">
     <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>
         <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>
         <default>
             <value>false</value>
             <value>false</value>
@@ -3106,21 +3099,21 @@
         </default>
         </default>
     </setting>
     </setting>
     <setting key="ldap.edirectory.cr.minRandomDuringSetup" level="1" required="true">
     <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>
         <description><![CDATA[]]></description>
         <default>
         <default>
             <value>0</value>
             <value>0</value>
         </default>
         </default>
     </setting>
     </setting>
     <setting key="ldap.edirectory.cr.applyWordlist" level="1" required="true">
     <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>
         <description><![CDATA[]]></description>
         <default>
         <default>
             <value>false</value>
             <value>false</value>
         </default>
         </default>
     </setting>
     </setting>
     <setting key="ldap.edirectory.cr.maxQuestionCharsInAnswer" level="1" required="true">
     <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>
         <description><![CDATA[]]></description>
         <default>
         <default>
             <value>0</value>
             <value>0</value>
@@ -3180,13 +3173,8 @@
     </setting>
     </setting>
     <setting key="helpdesk.filter" level="1">
     <setting key="helpdesk.filter" level="1">
         <label>Helpdesk Search Filter</label>
         <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>
     <setting key="helpdesk.result.form" level="1" required="true">
     <setting key="helpdesk.result.form" level="1" required="true">
         <label>Helpdesk Search Form</label>
         <label>Helpdesk Search Form</label>
@@ -3386,6 +3374,13 @@
             <option value="no">False</option>
             <option value="no">False</option>
         </options>
         </options>
     </setting>
     </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">
     <setting key="helpdesk.clearOtp.button" level="2" hidden="false">
         <label>Enable Clear One Time Password Settings Button</label>
         <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>
         <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>
         <label>External Web Services Permissions</label>
         <description><![CDATA[Users permitted to execute REST web services.]]></description>
         <description><![CDATA[Users permitted to execute REST web services.]]></description>
         <default syntaxVersion="2">
         <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>
         <default syntaxVersion="2" template="AD">
         <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>
         <default syntaxVersion="2" template="ORACLE_DS">
         <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>
         </default>
     </setting>
     </setting>
     <setting key="webservices.thirdParty.queryMatch" level="2">
     <setting key="webservices.thirdParty.queryMatch" level="2">
         <label>Web Services Third Party Permissions</label>
         <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>
         <description><![CDATA[Users permitted to execute REST web services and specify a third party via the 'username' parameter.]]></description>
         <default syntaxVersion="2">
         <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>
         <default syntaxVersion="2" template="AD">
         <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>
         <default syntaxVersion="2" template="ORACLE_DS">
         <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>
         </default>
     </setting>
     </setting>
     <setting key="external.macros.urls" level="2">
     <setting key="external.macros.urls" level="2">
@@ -4144,7 +4139,7 @@
         <description><![CDATA[NetIQ eDirectory specific settings.]]></description>
         <description><![CDATA[NetIQ eDirectory specific settings.]]></description>
     </category>
     </category>
     <category key="EDIR_CR_SETTINGS">
     <category key="EDIR_CR_SETTINGS">
-        <label>eDirectory CR Settings</label>
+        <label>eDirectory Challenge Sets</label>
         <description><![CDATA[NetIQ eDirectory CR specific settings.]]></description>
         <description><![CDATA[NetIQ eDirectory CR specific settings.]]></description>
     </category>
     </category>
     <category key="ACTIVE_DIRECTORY">
     <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("<div class=\"changeLogKey\">");
                         output.append(keyName);
                         output.append(keyName);
                         output.append("</div><div class=\"changeLogValue\">");
                         output.append("</div><div class=\"changeLogValue\">");
-                        output.append(value);
+                        output.append(StringUtil.escapeHtml(value));
                         output.append("</div>");
                         output.append("</div>");
                     } else {
                     } else {
                         output.append(keyName);
                         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
                     throws PwmOperationalException
             {
             {
-                final Map<String, EmailItemBean> values = new HashMap<>();
+                final Map<String, EmailItemBean> values = new TreeMap<>();
                 {
                 {
                     final List valueElements = settingElement.getChildren("value");
                     final List valueElements = settingElement.getChildren("value");
                     for (final Object loopValue : valueElements) {
                     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 boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
         
         
         final String rawValue = httpServletRequest.getParameter(name);
         final String rawValue = httpServletRequest.getParameter(name);
-        if (rawValue != null) {
+        if (rawValue != null && !rawValue.isEmpty()) {
             final String decodedValue = decodeStringToDefaultCharSet(rawValue);
             final String decodedValue = decodeStringToDefaultCharSet(rawValue);
             final String sanitizedValue = Validator.sanitizeInputValue(configuration, decodedValue, maxLength);
             final String sanitizedValue = Validator.sanitizeInputValue(configuration, decodedValue, maxLength);
             if (sanitizedValue != null) {
             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.FileItemIterator;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.h2.util.StringUtils;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.Validator;
 import password.pwm.Validator;
@@ -84,6 +83,7 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
         HIDE_HEADER_BUTTONS,
         HIDE_HEADER_BUTTONS,
         HIDE_HEADER_WARNINGS,
         HIDE_HEADER_WARNINGS,
         NO_REQ_COUNTER,
         NO_REQ_COUNTER,
+        ALWAYS_EXPAND_MESSAGE_TEXT,
     }
     }
 
 
     public static PwmRequest forRequest(
     public static PwmRequest forRequest(
@@ -339,15 +339,15 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
             return false;
             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();
         final StringBuilder redirectURL = new StringBuilder();
         redirectURL.append(this.getHttpServletRequest().getContextPath());
         redirectURL.append(this.getHttpServletRequest().getContextPath());
         redirectURL.append(this.getHttpServletRequest().getServletPath());
         redirectURL.append(this.getHttpServletRequest().getServletPath());
         redirectURL.append("?");
         redirectURL.append("?");
         redirectURL.append(PwmConstants.PARAM_ACTION_REQUEST).append("=enterCode");
         redirectURL.append(PwmConstants.PARAM_ACTION_REQUEST).append("=enterCode");
         redirectURL.append("&");
         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);
         LOGGER.debug(pwmSession, "detected long servlet url, redirecting user to " + redirectURL);
         sendRedirect(redirectURL.toString());
         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);
         return checkIfStartsWithURL("/public/" + PwmConstants.URL_SERVLET_NEW_USER);
     }
     }
 
 
+    public boolean isOauthConsumer() {
+        return checkIfStartsWithURL("/public/" + PwmConstants.URL_SERVLET_OAUTH_CONSUMER);
+    }
+
     public boolean isPrivateUrl() {
     public boolean isPrivateUrl() {
         return checkIfStartsWithURL("/private/");
         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;
             return true;
         }
         }
 
 
+        // allow oauth
+        if (pwmURL.isOauthConsumer()) {
+            return false;
+        }
+
         // block if public request and not running or in trial
         // block if public request and not running or in trial
         if (!PwmConstants.TRIAL_MODE) {
         if (!PwmConstants.TRIAL_MODE) {
             if (pwmURL.isPublicUrl() && !pwmURL.isLogoutURL() && !pwmURL.isCommandServletURL()) {
             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 PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final HttpServletRequest req = pwmRequest.getHttpServletRequest();
         final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-        final SessionStateBean ssBean = pwmSession.getSessionStateBean();
 
 
         //try to authenticate user with basic auth
         //try to authenticate user with basic auth
         if (!pwmSession.getSessionStateBean().isAuthenticated()) {
         if (!pwmSession.getSessionStateBean().isAuthenticated()) {
@@ -405,7 +404,7 @@ public class AuthenticationFilter extends AbstractPwmFilter {
     static boolean processOAuthAuthenticationRequest(
     static boolean processOAuthAuthenticationRequest(
             final PwmRequest pwmRequest
             final PwmRequest pwmRequest
     )
     )
-            throws IOException, ServletException
+            throws IOException, ServletException, PwmUnrecoverableException
 
 
     {
     {
         final Configuration config = pwmRequest.getConfig();
         final Configuration config = pwmRequest.getConfig();
@@ -414,7 +413,13 @@ public class AuthenticationFilter extends AbstractPwmFilter {
             return false;
             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 redirectUri = OAuthConsumerServlet.figureOauthSelfEndPointUrl(pwmRequest);
         final String code = config.readAppProperty(AppProperty.OAUTH_ID_REQUEST_TYPE);
         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);
         final String redirectUrl = ServletHelper.appendAndEncodeUrlParameters(settings.getLoginURL(), urlParams);
 
 
+        LOGGER.trace(pwmRequest, "preparing to start oauth authentication request sequence, set originally requested url: " + originalURL);
+
         try{
         try{
             pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress(true);
             pwmRequest.getPwmSession().getSessionStateBean().setOauthInProgress(true);
             pwmRequest.sendRedirect(redirectUrl);
             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 PwmRequest pwmRequest,
             final PwmFilterChain chain
             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 PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final Configuration config = pwmRequest.getConfig();
         final Configuration config = pwmRequest.getConfig();
+
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final SessionStateBean ssBean = pwmSession.getSessionStateBean();
         final SessionStateBean ssBean = pwmSession.getSessionStateBean();
         final PwmResponse resp = pwmRequest.getPwmResponse();
         final PwmResponse resp = pwmRequest.getPwmResponse();
@@ -85,7 +115,7 @@ public class SessionFilter extends AbstractPwmFilter {
             if (PwmError.ERROR_INTRUDER_SESSION != e.getError()) {
             if (PwmError.ERROR_INTRUDER_SESSION != e.getError()) {
                 pwmRequest.invalidateSession();
                 pwmRequest.invalidateSession();
             }
             }
-            return;
+            return false;
         }
         }
 
 
         // mark last url
         // 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");
             LOGGER.warn("invalidating session due to dirty page leave time greater then configured timeout");
             pwmRequest.invalidateSession();
             pwmRequest.invalidateSession();
             resp.sendRedirect(pwmRequest.getHttpServletRequest().getRequestURI());
             resp.sendRedirect(pwmRequest.getHttpServletRequest().getRequestURI());
-            return;
+            return false;
         }
         }
 
 
         //override session locale due to parameter
         //override session locale due to parameter
@@ -122,7 +152,7 @@ public class SessionFilter extends AbstractPwmFilter {
         // make sure connection is secure.
         // make sure connection is secure.
         if (config.readSettingAsBoolean(PwmSetting.REQUIRE_HTTPS) && !pwmRequest.getHttpServletRequest().isSecure()) {
         if (config.readSettingAsBoolean(PwmSetting.REQUIRE_HTTPS) && !pwmRequest.getHttpServletRequest().isSecure()) {
             pwmRequest.respondWithError(PwmError.ERROR_SECURE_REQUEST_REQUIRED.toInfo());
             pwmRequest.respondWithError(PwmError.ERROR_SECURE_REQUEST_REQUIRED.toInfo());
-            return;
+            return false;
         }
         }
 
 
         //check for session verification failure
         //check for session verification failure
@@ -134,7 +164,7 @@ public class SessionFilter extends AbstractPwmFilter {
                 ssBean.setSessionVerified(true);
                 ssBean.setSessionVerified(true);
             } else {
             } else {
                 if (verifySession(pwmRequest, mode)) {
                 if (verifySession(pwmRequest, mode)) {
-                    return;
+                    return false;
                 }
                 }
             }
             }
         }
         }
@@ -147,7 +177,7 @@ public class SessionFilter extends AbstractPwmFilter {
                 } catch (PwmOperationalException e) {
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
-                    return;
+                    return false;
                 }
                 }
                 ssBean.setForwardURL(forwardURL);
                 ssBean.setForwardURL(forwardURL);
                 LOGGER.debug(pwmRequest, "forwardURL parameter detected in request, setting session forward url to " + 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) {
                 } catch (PwmOperationalException e) {
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     LOGGER.error(pwmRequest, e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
                     pwmRequest.respondWithError(e.getErrorInformation());
-                    return;
+                    return false;
                 }
                 }
                 ssBean.setLogoutURL(logoutURL);
                 ssBean.setLogoutURL(logoutURL);
                 LOGGER.debug(pwmRequest, "logoutURL parameter detected in request, setting session logout url to " + 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);
             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.
         // update last request time.
         ssBean.setSessionLastAccessedTime(new Date());
         ssBean.setSessionLastAccessedTime(new Date());
 
 
         if (pwmApplication.getStatisticsManager() != null) {
         if (pwmApplication.getStatisticsManager() != null) {
             pwmApplication.getStatisticsManager().incrementValue(Statistic.HTTP_REQUESTS);
             pwmApplication.getStatisticsManager().incrementValue(Statistic.HTTP_REQUESTS);
         }
         }
+
+        return true;
     }
     }
 
 
     public void destroy() {
     public void destroy() {

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

@@ -22,6 +22,9 @@
 
 
 package password.pwm.http.servlet;
 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 com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
@@ -37,6 +40,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClientRequest;
 import password.pwm.http.client.PwmHttpClientRequest;
 import password.pwm.http.client.PwmHttpClientResponse;
 import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.util.JsonUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.ServletHelper;
 import password.pwm.util.ServletHelper;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.TimeDuration;
@@ -46,8 +50,10 @@ import password.pwm.util.stats.StatisticsManager;
 
 
 import javax.servlet.ServletException;
 import javax.servlet.ServletException;
 import java.io.IOException;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.List;
 
 
 
 
 public class CaptchaServlet extends PwmServlet {
 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 PasswordData privateKey = pwmApplication.getConfig().readSettingAsPassword(PwmSetting.RECAPTCHA_KEY_PRIVATE);
 
 
         final StringBuilder bodyText = new StringBuilder();
         final StringBuilder bodyText = new StringBuilder();
-        bodyText.append("privatekey=").append(privateKey.getStringValue());
+        bodyText.append("secret=").append(privateKey.getStringValue());
         bodyText.append("&");
         bodyText.append("&");
         bodyText.append("remoteip=").append(pwmRequest.getSessionLabel().getSrcAddress());
         bodyText.append("remoteip=").append(pwmRequest.getSessionLabel().getSrcAddress());
         bodyText.append("&");
         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 {
         try {
             final PwmHttpClientRequest clientRequest = new PwmHttpClientRequest(
             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) {
         } catch (Exception e) {
             final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage();
             final String errorMsg = "unexpected error during reCaptcha API execution: " + e.getMessage();
             LOGGER.error(errorMsg,e);
             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);
             configManagerBean.setConfiguration(loadedConfig);
         }
         }
 
 
+        pwmSession.setSessionTimeout(
+                pwmRequest.getHttpServletRequest().getSession(),
+                Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_EDITOR_IDLE_TIMEOUT)));
+
+
         final ConfigEditorAction action = readProcessAction(pwmRequest);
         final ConfigEditorAction action = readProcessAction(pwmRequest);
 
 
         if (action != null) {
         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);
             final ContextManager contextManager = ContextManager.getContextManager(pwmRequest);
             try {
             try {
                 writeConfig(contextManager, configGuideBean);
                 writeConfig(contextManager, configGuideBean);
-            } catch (PwmOperationalException e) {
+            } catch (PwmException e) {
                 final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
                 final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
                 pwmRequest.outputJsonResult(restResultBean);
                 pwmRequest.outputJsonResult(restResultBean);
                 return;
                 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<>();
             final HashMap<String,String> resultData = new HashMap<>();
             resultData.put("serverRestart","true");
             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) {
         if (forgottenPasswordBean.getUserInfo() == null) {
             pwmRequest.sendRedirectToContinue();
             pwmRequest.sendRedirectToContinue();
-            return;
         }
         }
     }
     }
 
 
@@ -310,7 +309,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
         final List<FormConfiguration> forgottenPasswordForm = pwmApplication.getConfig().readSettingAsForm(
         final List<FormConfiguration> forgottenPasswordForm = pwmApplication.getConfig().readSettingAsForm(
                 PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM);
                 PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM);
 
 
-        Map<FormConfiguration, String> formValues = new HashMap();
+        Map<FormConfiguration, String> formValues = new HashMap<>();
 
 
         try {
         try {
             //read the values from the request
             //read the values from the request
@@ -1102,7 +1101,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
                         userInfoBean.getUserIdentity(),
                         userInfoBean.getUserIdentity(),
                         theUser
                         theUser
                 );
                 );
-                challengeSet = responseSet == null ? null : responseSet.getChallengeSet();
+                challengeSet = responseSet == null ? null : responseSet.getPresentableChallengeSet();
             } catch (ChaiValidationException e) {
             } catch (ChaiValidationException e) {
                 final String errorMsg = "unable to determine presentable challengeSet for stored responses: " + e.getMessage();
                 final String errorMsg = "unable to determine presentable challengeSet for stored responses: " + e.getMessage();
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NO_CHALLENGES, errorMsg);
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_NO_CHALLENGES, errorMsg);
@@ -1319,6 +1318,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
             break;
             break;
 
 
             case CHALLENGE_RESPONSES: {
             case CHALLENGE_RESPONSES: {
+                pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.ForgottenPasswordChallengeSet, forgottenPasswordBean.getPresentableChallengeSet());
                 pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_RESPONSES);
                 pwmRequest.forwardToJsp(PwmConstants.JSP_URL.RECOVER_PASSWORD_RESPONSES);
             }
             }
             break;
             break;

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

@@ -84,7 +84,6 @@ import java.util.*;
 public class HelpdeskServlet extends PwmServlet {
 public class HelpdeskServlet extends PwmServlet {
 
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskServlet.class);
     private static final PwmLogger LOGGER = PwmLogger.forClass(HelpdeskServlet.class);
-    private static final String TOKEN_NAME = HelpdeskServlet.class.getName();
 
 
     public enum HelpdeskAction implements PwmServlet.ProcessAction {
     public enum HelpdeskAction implements PwmServlet.ProcessAction {
         doUnlock(HttpMethod.POST),
         doUnlock(HttpMethod.POST),
@@ -286,7 +285,7 @@ public class HelpdeskServlet extends PwmServlet {
 
 
         // check user identity matches helpdesk bean user
         // check user identity matches helpdesk bean user
         final UserInfoBean detailUserInfo = helpdeskBean.getHeldpdeskDetailInfo().getUserInfoBean();
         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.setResponseError(new ErrorInformation(PwmError.ERROR_UNKNOWN,"requested user for delete  is not currently selected user"));
             pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
             pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_SEARCH);
             return;
             return;
@@ -369,7 +368,7 @@ public class HelpdeskServlet extends PwmServlet {
             final String errorMsg = "cannot select self";
             final String errorMsg = "cannot select self";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED,errorMsg);
             LOGGER.debug(pwmRequest, errorInformation);
             LOGGER.debug(pwmRequest, errorInformation);
-            pwmRequest.respondWithError(errorInformation);
+            pwmRequest.respondWithError(errorInformation, false);
             return;
             return;
         }
         }
 
 
@@ -411,7 +410,8 @@ public class HelpdeskServlet extends PwmServlet {
         searchConfiguration.setEnableContextValidation(false);
         searchConfiguration.setEnableContextValidation(false);
         searchConfiguration.setUsername(username);
         searchConfiguration.setUsername(username);
         searchConfiguration.setEnableValueEscaping(false);
         searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setFilter(helpdeskProfile.readSettingAsString(PwmSetting.HELPDESK_SEARCH_FILTER));
+        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig(),helpdeskProfile));
+
         if (!useProxy) {
         if (!useProxy) {
             final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity();
             final UserIdentity loggedInUser = pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity();
             searchConfiguration.setLdapProfile(loggedInUser.getLdapProfileID());
             searchConfiguration.setLdapProfile(loggedInUser.getLdapProfileID());
@@ -805,6 +805,32 @@ public class HelpdeskServlet extends PwmServlet {
         pwmRequest.forwardToJsp(PwmConstants.JSP_URL.HELPDESK_DETAIL);
         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(
     private static ChaiUser getChaiUser(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
             final HelpdeskProfile helpdeskProfile,
             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();
         final NewUserBean newUserBean = pwmSession.getNewUserBean();
 
 
         if (action != null) {
         if (action != null) {
-            pwmRequest.validatePwmFormID();
             switch (action) {
             switch (action) {
                 case checkProgress:
                 case checkProgress:
                     restCheckProgress(pwmRequest, newUserBean);
                     restCheckProgress(pwmRequest, newUserBean);
@@ -322,6 +321,7 @@ public class NewUserServlet extends PwmServlet {
             pwmRequest.outputJsonResult(restResultBean);
             pwmRequest.outputJsonResult(restResultBean);
         } catch (PwmOperationalException e) {
         } catch (PwmOperationalException e) {
             final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
             final RestResultBean restResultBean = RestResultBean.fromError(e.getErrorInformation(), pwmRequest);
+            LOGGER.error(pwmRequest, "error while validating new user form: " + e.getMessage());
             pwmRequest.outputJsonResult(restResultBean);
             pwmRequest.outputJsonResult(restResultBean);
         }
         }
     }
     }
@@ -406,12 +406,14 @@ public class NewUserServlet extends PwmServlet {
                     newUserBean.setNewUserForm(newUserFormFromToken);
                     newUserBean.setNewUserForm(newUserFormFromToken);
                     newUserBean.setFormPassed(true);
                     newUserBean.setFormPassed(true);
                     newUserBean.setEmailTokenPassed(true);
                     newUserBean.setEmailTokenPassed(true);
+                    newUserBean.setEmailTokenIssued(true);
                     newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                     newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                     tokenPassed = true;
                     tokenPassed = true;
                 } else if (NewUserBean.NewUserVerificationPhase.SMS.getTokenName().equals(tokenPayload.getName())) {
                 } else if (NewUserBean.NewUserVerificationPhase.SMS.getTokenName().equals(tokenPayload.getName())) {
                     if (newUserBean.getNewUserForm() != null && newUserBean.getNewUserForm().isConsistentWith(newUserFormFromToken)) {
                     if (newUserBean.getNewUserForm() != null && newUserBean.getNewUserForm().isConsistentWith(newUserFormFromToken)) {
                         LOGGER.debug(pwmRequest, "SMS token passed");
                         LOGGER.debug(pwmRequest, "SMS token passed");
                         newUserBean.setSmsTokenPassed(true);
                         newUserBean.setSmsTokenPassed(true);
+                        newUserBean.setSmsTokenIssued(true);
                         newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                         newUserBean.setVerificationPhase(NewUserBean.NewUserVerificationPhase.NONE);
                         tokenPassed = true;
                         tokenPassed = true;
                     } else {
                     } 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.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 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.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.http.bean.LoginInfoBean;
 import password.pwm.http.client.PwmHttpClient;
 import password.pwm.http.client.PwmHttpClient;
+import password.pwm.ldap.UserSearchEngine;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.util.*;
 import password.pwm.util.*;
@@ -59,6 +58,14 @@ import java.util.Map;
 
 
 public class OAuthConsumerServlet extends PwmServlet {
 public class OAuthConsumerServlet extends PwmServlet {
     private static final PwmLogger LOGGER = PwmLogger.forClass(OAuthConsumerServlet.class);
     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
     @Override
     protected ProcessAction readProcessAction(PwmRequest request)
     protected ProcessAction readProcessAction(PwmRequest request)
@@ -72,15 +79,24 @@ public class OAuthConsumerServlet extends PwmServlet {
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
     {
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final Configuration config = pwmApplication.getConfig();
+        final Configuration config = pwmRequest.getConfig();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final Settings settings = Settings.fromConfiguration(pwmApplication.getConfig());
         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 String errorMsg = "oauth consumer reached, but oauth authentication has not yet been initiated.";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
-            LOGGER.error(pwmSession, errorMsg);
             pwmRequest.respondWithError(errorInformation);
             pwmRequest.respondWithError(errorInformation);
+            LOGGER.error(pwmSession, errorMsg);
             return;
             return;
         }
         }
 
 
@@ -88,32 +104,30 @@ public class OAuthConsumerServlet extends PwmServlet {
         if (oauthRequestError != null && !oauthRequestError.isEmpty()) {
         if (oauthRequestError != null && !oauthRequestError.isEmpty()) {
             final String errorMsg = "error detected from oauth request parameter: " + oauthRequestError;
             final String errorMsg = "error detected from oauth request parameter: " + oauthRequestError;
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg,"Remote Error: " + oauthRequestError,null);
             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);
             pwmRequest.respondWithError(errorInformation);
             return;
             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);
         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()) {
         if (requestStateStr == null || requestStateStr.isEmpty()) {
             final String errorMsg = "state parameter is missing from oauth request";
             final String errorMsg = "state parameter is missing from oauth request";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_OAUTH_ERROR,errorMsg);
             LOGGER.error(pwmSession,errorMsg);
             LOGGER.error(pwmSession,errorMsg);
             pwmRequest.respondWithError(errorInformation);
             pwmRequest.respondWithError(errorInformation);
             return;
             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));
         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;
         final OAuthResolveResults resolveResults;
         {
         {
@@ -150,34 +164,44 @@ public class OAuthConsumerServlet extends PwmServlet {
 
 
         LOGGER.debug(pwmSession, "received user login id value from OAuth server: " + oauthSuppliedUsername);
         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.
             // recycle the session to prevent session fixation attack.
             pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded(true);
             pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded(true);
 
 
             // see if there is a an original request url
             // 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) {
         } 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);
             pwmRequest.respondWithError(errorInformation);
             return;
             return;
         }
         }
@@ -507,4 +531,48 @@ public class OAuthConsumerServlet extends PwmServlet {
             this.refreshToken = refreshToken;
             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);
         final PeopleSearchActions peopleSearchAction = this.readProcessAction(pwmRequest);
         if (peopleSearchAction != null) {
         if (peopleSearchAction != null) {
             switch (peopleSearchAction) {
             switch (peopleSearchAction) {
@@ -334,7 +337,7 @@ public class PeopleSearchServlet extends PwmServlet {
         final List<FormConfiguration> detailFormConfig = config.readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
         final List<FormConfiguration> detailFormConfig = config.readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
         final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
         final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
                 detailFormConfig, pwmSession.getSessionStateBean().getLocale());
                 detailFormConfig, pwmSession.getSessionStateBean().getLocale());
-        final ChaiUser theUser = config.readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY)
+        final ChaiUser theUser = useProxy(pwmApplication,pwmSession)
                 ? pwmApplication.getProxiedChaiUser(userIdentity)
                 ? pwmApplication.getProxiedChaiUser(userIdentity)
                 : pwmSession.getSessionManager().getActor(pwmApplication, userIdentity);
                 : pwmSession.getSessionManager().getActor(pwmApplication, userIdentity);
         Map<String, String> values = null;
         Map<String, String> values = null;
@@ -351,7 +354,8 @@ public class PeopleSearchServlet extends PwmServlet {
     private void restUserDetailRequest(
     private void restUserDetailRequest(
             final PwmRequest pwmRequest
             final PwmRequest pwmRequest
     )
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException {
+            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+    {
         final Date startTime = new Date();
         final Date startTime = new Date();
         final Map<String, String> valueMap = pwmRequest.readBodyAsJsonStringMap();
         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 useProxy = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY);
         final boolean publicAccessEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC);
         final boolean publicAccessEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC);
+
         if (useProxy) {
         if (useProxy) {
             return true;
             return true;
         }
         }
@@ -689,6 +694,6 @@ public class PeopleSearchServlet extends PwmServlet {
 
 
     private static Set<String> getSearchAttributes(final Configuration configuration) {
     private static Set<String> getSearchAttributes(final Configuration configuration) {
         final List<String> searchResultForm = configuration.readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES);
         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 ErrorInformation errorInformation = e.getErrorInformation();
                     final PwmSession pwmSession = PwmSessionWrapper.readPwmSession(req);
                     final PwmSession pwmSession = PwmSessionWrapper.readPwmSession(req);
                     LOGGER.error(pwmSession, errorInformation.toDebugStr());
                     LOGGER.error(pwmSession, errorInformation.toDebugStr());
-                    pwmRequest.respondWithError(errorInformation);
+                    pwmRequest.respondWithError(errorInformation,false);
                     return;
                     return;
                 }
                 }
                 throw e;
                 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");
         LOGGER.trace(sessionLabel, "loading existing user profile data from ldap");
         final Map<String,String> userData = new LinkedHashMap<>();
         final Map<String,String> userData = new LinkedHashMap<>();
         try {
         try {
-            userData.putAll(userDataReader.readStringAttributes(FormConfiguration.convertToListOfNames(formFields)));
+            userData.putAll(userDataReader.readStringAttributes(FormConfiguration.convertToListOfNames(formFields), true));
         } catch (ChaiOperationException e) {
         } catch (ChaiOperationException e) {
             LOGGER.error(sessionLabel, "unexpected error reading profile data attributes: " + e.getMessage());
             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_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_AutoGeneratedPassword=Auto-generate a new password
 Display_CapsLockIsOn=CAPS LOCK is on
 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_CaptchaInputWords=Enter the text displayed above
 Display_CaptchaInputNumbers=Enter the numbers you hear
 Display_CaptchaInputNumbers=Enter the numbers you hear
 Display_CaptchaGetAudio=Get an audio CAPTCHA
 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_CryptError=An unexpected cryptography error has occurred.
 Error_SmsSendError=Unable to send sms message: %1%
 Error_SmsSendError=Unable to send sms message: %1%
 Error_LdapDataError=An LDAP data error has occurred.
 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_NoProfileAssigned=No profile is assigned for this operation.
 
 
 Error_ConfigUploadSuccess=File uploaded successfully
 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 password.pwm.util.stats.Statistic;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
-import java.math.BigInteger;
 import java.util.*;
 import java.util.*;
 
 
 public class UserSearchEngine {
 public class UserSearchEngine {
@@ -68,15 +67,6 @@ public class UserSearchEngine {
         this(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
         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(
     private static String figureSearchFilterForParams(
             final Map<FormConfiguration, String> formValues,
             final Map<FormConfiguration, String> formValues,
             final String searchFilter,
             final String searchFilter,
@@ -382,8 +372,9 @@ public class UserSearchEngine {
         searchHelper.setFilter(searchFilter);
         searchHelper.setFilter(searchFilter);
         searchHelper.setAttributes(returnAttributes);
         searchHelper.setAttributes(returnAttributes);
         searchHelper.setTimeLimit((int)timeoutMs);
         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);
         LOGGER.debug(sessionLabel, "performing ldap search for user; " + debugInfo);
 
 
         final Date startTime = new Date();
         final Date startTime = new Date();

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

@@ -239,14 +239,15 @@ class AuthenticationRequest {
                 sessionLabel.getSrcHostname()
                 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() {
     private void initialize() {
         if (startTime != null) {
         if (startTime != null) {
             throw new IllegalStateException("AuthenticationRequest can not be used more than once");
             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();
         startTime = new Date();
     }
     }
 
 

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

@@ -371,6 +371,12 @@ public class ServletHelper {
         if (ssBean.getLocale() == null) {
         if (ssBean.getLocale() == null) {
             initializeLocaleAndTheme(pwmRequest.getHttpServletRequest(), pwmApplication, pwmSession);
             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(
     private static void initializeLocaleAndTheme(

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

@@ -22,8 +22,10 @@
 
 
 package password.pwm.util.cli;
 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 org.apache.log4j.varia.NullAppender;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
@@ -34,12 +36,19 @@ import password.pwm.util.Helper;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBException;
 import password.pwm.util.localdb.LocalDBFactory;
 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.File;
+import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.OutputStreamWriter;
 import java.util.*;
 import java.util.*;
 
 
 public class MainClass {
 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;
     public static final Map<String,CliCommand> COMMANDS;
     static {
     static {
@@ -99,7 +108,7 @@ public class MainClass {
             throws Exception
             throws Exception
     {
     {
         final Map<String,Object> options = parseCommandOptions(parameters, args);
         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 File configurationFile = locateConfigurationFile(applicationPath);
 
 
         final ConfigurationReader configReader = loadConfiguration(configurationFile);
         final ConfigurationReader configReader = loadConfiguration(configurationFile);
@@ -109,7 +118,7 @@ public class MainClass {
                 : null;
                 : null;
         final LocalDB localDB = parameters.needsLocalDB
         final LocalDB localDB = parameters.needsLocalDB
                 ? pwmApplication == null
                 ? pwmApplication == null
-                ? loadPwmDB(config, parameters.readOnly)
+                ? loadPwmDB(config, parameters.readOnly, applicationPath)
                 : pwmApplication.getLocalDB()
                 : pwmApplication.getLocalDB()
                 : null;
                 : null;
 
 
@@ -189,10 +198,14 @@ public class MainClass {
         return returnObj;
         return returnObj;
     }
     }
 
 
-    public static void main(final String[] args)
+    static MainOptions MAIN_OPTIONS = new MainOptions();
+
+    public static void main(String[] args)
             throws Exception
             throws Exception
     {
     {
-        initLog4j(false);
+        args = parseCommandOptions(args);
+
+        initLog4j(MAIN_OPTIONS.pwmLogLevel);
 
 
         final String commandStr = args == null || args.length < 1 ? null : args[0];
         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().removeAllAppenders();
             Logger.getRootLogger().addAppender(new NullAppender());
             Logger.getRootLogger().addAppender(new NullAppender());
             return;
             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 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 File databaseDirectory;
         final String pwmDBLocationSetting = config.readSettingAsString(PwmSetting.PWMDB_LOCATION);
         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);
         return LocalDBFactory.getInstance(databaseDirectory, readonly, null, config);
     }
     }
 
 
@@ -288,4 +352,26 @@ public class MainClass {
     private static void out(CharSequence txt) {
     private static void out(CharSequence txt) {
         System.out.println(txt + "\n");
         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) {
         } catch (Exception e) {
+            LOGGER.error("error while examining LocalDB: " + e.getMessage());
+        } finally {
             if (iter != null) {
             if (iter != null) {
                 iter.close();
                 iter.close();
             }
             }

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

@@ -43,7 +43,7 @@ import java.util.List;
 public class PwmLogManager {
 public class PwmLogManager {
     private static final PwmLogger LOGGER = PwmLogger.forClass(PwmLogManager.class);
     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(),
             PwmApplication.class.getPackage(),
             ChaiUser.class.getPackage(),
             ChaiUser.class.getPackage(),
             Package.getPackage("org.jasig.cas.client")
             Package.getPackage("org.jasig.cas.client")
@@ -54,6 +54,7 @@ public class PwmLogManager {
         for (final Package logPackage : LOGGING_PACKAGES) {
         for (final Package logPackage : LOGGING_PACKAGES) {
             if (logPackage != null) {
             if (logPackage != null) {
                 final Logger logger = Logger.getLogger(logPackage.getName());
                 final Logger logger = Logger.getLogger(logPackage.getName());
+                logger.setAdditivity(false);
                 logger.removeAllAppenders();
                 logger.removeAllAppenders();
                 logger.setLevel(Level.TRACE);
                 logger.setLevel(Level.TRACE);
             }
             }
@@ -72,8 +73,6 @@ public class PwmLogManager {
             final File pwmApplicationPath,
             final File pwmApplicationPath,
             final String fileLogLevel
             final String fileLogLevel
     ) {
     ) {
-        deinitializeLogger();
-
         PwmLogger.setPwmApplication(pwmApplication);
         PwmLogger.setPwmApplication(pwmApplication);
 
 
         // try to configure using the log4j config file (if it exists)
         // 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)
         // 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));
         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 PwmLogLevel minimumDbLogLevel;
     private static PwmApplication pwmApplication;
     private static PwmApplication pwmApplication;
     private static RollingFileAppender fileAppender;
     private static RollingFileAppender fileAppender;
+    private static boolean initialized;
 
 
     private final String name;
     private final String name;
     private final org.apache.log4j.Logger log4jLogger;
     private final org.apache.log4j.Logger log4jLogger;
     private final boolean localDBDisabled;
     private final boolean localDBDisabled;
 
 
-
+    public static void markInitialized() {
+        initialized = true;
+    }
 
 
     static void setPwmApplication(final PwmApplication pwmApplication) {
     static void setPwmApplication(final PwmApplication pwmApplication) {
         PwmLogger.pwmApplication = pwmApplication;
         PwmLogger.pwmApplication = pwmApplication;
+        if (pwmApplication != null) {
+            initialized = true;
+        }
     }
     }
 
 
     static void setLocalDBLogger(final PwmLogLevel minimumDbLogLevel, final LocalDBLogger localDBLogger) {
     static void setLocalDBLogger(final PwmLogLevel minimumDbLogLevel, final LocalDBLogger localDBLogger) {
@@ -175,7 +181,7 @@ public class PwmLogger {
         final Throwable throwable = logEvent.getThrowable();
         final Throwable throwable = logEvent.getThrowable();
         final PwmLogLevel level = logEvent.getLevel();
         final PwmLogLevel level = logEvent.getLevel();
 
 
-        if (pwmApplication != null) {
+        if (initialized) {
             switch (level) {
             switch (level) {
                 case DEBUG:
                 case DEBUG:
                     log4jLogger.debug(wrappedMessage, throwable);
                     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.http.bean.LoginInfoBean;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.ldap.UserDataReader;
 import password.pwm.util.PwmRandom;
 import password.pwm.util.PwmRandom;
+import password.pwm.util.StringUtil;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
@@ -52,6 +53,10 @@ public abstract class StandardMacros {
     public static final List<Class<? extends MacroImplementation>> STANDARD_MACROS;
     public static final List<Class<? extends MacroImplementation>> STANDARD_MACROS;
     static {
     static {
         final List<Class<? extends MacroImplementation>> defaultMacros = new ArrayList<>();
         final List<Class<? extends MacroImplementation>> defaultMacros = new ArrayList<>();
+
+        // wrapper macros
+        defaultMacros.add(EncodingMacro.class);
+
         defaultMacros.add(LdapMacro.class);
         defaultMacros.add(LdapMacro.class);
         defaultMacros.add(UserPwExpirationTimeMacro.class);
         defaultMacros.add(UserPwExpirationTimeMacro.class);
         defaultMacros.add(UserPwExpirationTimeDefaultMacro.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();
                 final MacroMachine macroMachine = settings.getMacroMachine();
 
 
                 url = macroMachine.expandMacros(url, new MacroMachine.URLEncoderReplacer());
                 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()) {
                 for (final String headerName : headers.keySet()) {
                     final String headerValue = headers.get(headerName);
                     final String headerValue = headers.get(headerName);
@@ -152,13 +148,13 @@ public class ActionExecutor {
             final PwmHttpClient client = new PwmHttpClient(pwmApplication, pwmSession);
             final PwmHttpClient client = new PwmHttpClient(pwmApplication, pwmSession);
             final PwmHttpClientResponse clientResponse = client.makeRequest(clientRequest);
             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) {
         } catch (Exception e) {
             if (e instanceof PwmOperationalException) {
             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
         // send email notification
         sendChangePasswordHelpdeskEmailNotice(pwmSession, pwmApplication, userInfoBean);
         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
         // send password
         final boolean sendPassword = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_SEND_PASSWORD);
         final boolean sendPassword = helpdeskProfile.readSettingAsBoolean(PwmSetting.HELPDESK_SEND_PASSWORD);
         if (sendPassword) {
         if (sendPassword) {

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

@@ -46,6 +46,7 @@
         <%@ include file="fragment/admin-nav.jsp" %>
         <%@ include file="fragment/admin-nav.jsp" %>
         <form action="Administration" method="post" enctype="application/x-www-form-urlencoded"
         <form action="Administration" method="post" enctype="application/x-www-form-urlencoded"
               name="searchForm" id="searchForm" class="pwm-form">
               name="searchForm" id="searchForm" class="pwm-form">
+            <input type="hidden" name="pwmFormID" value="<pwm:FormID/>"/>
             <table style="">
             <table style="">
                 <tr style="width:0">
                 <tr style="width:0">
                     <td class="key" style="border: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)
   ~ Password Management Servlets (PWM)
   ~ http://code.google.com/p/pwm/
   ~ http://code.google.com/p/pwm/
@@ -22,28 +21,21 @@
   --%>
   --%>
 
 
 <!DOCTYPE html>
 <!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" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <html dir="<pwm:LocaleOrientation/>">
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <body class="nihilo">
 <%-- begin reCaptcha section (http://code.google.com/apis/recaptcha/docs/display.html) --%>
 <%-- 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>
 <pwm:script>
     <script type="text/javascript">
     <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>
     </script>
 </pwm:script>
 </pwm:script>
+<pwm:script-ref url="<%=(String)JspUtility.getAttribute(pageContext,PwmConstants.REQUEST_ATTR.CaptchaClientUrl)%>"/>
 <div id="wrapper">
 <div id="wrapper">
     <jsp:include page="fragment/header-body.jsp">
     <jsp:include page="fragment/header-body.jsp">
         <jsp:param name="pwm.PageName" value="Title_Captcha"/>
         <jsp:param name="pwm.PageName" value="Title_Captcha"/>
@@ -52,48 +44,29 @@
         <p><pwm:display key="Display_Captcha"/></p>
         <p><pwm:display key="Display_Captcha"/></p>
         <%@ include file="fragment/message.jsp" %>
         <%@ include file="fragment/message.jsp" %>
         <br/>
         <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>
                     </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>
-            </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>
             </noscript>
+            --%>
             <div class="buttonbar">
             <div class="buttonbar">
                 <input type="hidden" name="processAction" value="doVerify"/>
                 <input type="hidden" name="processAction" value="doVerify"/>
                 <button type="submit" name="verify" class="btn" id="verify_button">
                 <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>
         <p><pwm:display key="Display_ChangePasswordForm"/></p>
         <%@ include file="fragment/message.jsp" %>
         <%@ include file="fragment/message.jsp" %>
         <br/>
         <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()) { %>
             <% if (JspUtility.getPwmSession(pageContext).getChangePasswordBean().isCurrentPasswordRequired()) { %>
             <h1>
             <h1>
                 <label for="currentPassword"><pwm:display key="Field_CurrentPassword"/></label>
                 <label for="currentPassword"><pwm:display key="Field_CurrentPassword"/></label>
             </h1>
             </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/>
             <br/>
             <% } %>
             <% } %>
             <jsp:include page="fragment/form.jsp"/>
             <jsp:include page="fragment/form.jsp"/>

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

@@ -52,7 +52,7 @@
     <div id="centerbody" >
     <div id="centerbody" >
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
         <p><pwm:display key="Display_PleaseWaitPassword"/></p>
         <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 data-dojo-type="dijit/ProgressBar" style="width:100%" data-dojo-id="passwordProgressBar" id="passwordProgressBar" data-dojo-props="maximum:100"></div>
         </div>
         </div>
         <div style="text-align: center; width: 100%; padding-top: 50px">
         <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/>/>
                         <input type="search" id="homeSettingSearch" name="homeSettingSearch" class="inputfield" <pwm:autofocus/>/>
                     </td>
                     </td>
                     <td>
                     <td>
-                        <div style="margin-top:5px">
+                        <div style="margin-top:5px; width:20px; max-width: 20px;">
                             <div id="indicator-searching" style="display: none">
                             <div id="indicator-searching" style="display: none">
                                 <span style="" class="fa fa-lg fa-spin fa-spinner"></span>
                                 <span style="" class="fa fa-lg fa-spin fa-spinner"></span>
                             </div>
                             </div>

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

@@ -46,7 +46,7 @@
         </div>
         </div>
     </div>
     </div>
     <div id="centerbody">
     <div id="centerbody">
-        <form id="widgetForm" name="widgetForm">
+        <form id="configForm" name="configForm">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <div id="outline_ldap-server" class="setting_outline">
             <div id="outline_ldap-server" class="setting_outline">
                 <div id="titlePaneHeader-ldap-server" class="setting_title">
                 <div id="titlePaneHeader-ldap-server" class="setting_title">
@@ -65,21 +65,7 @@
                         <tr>
                         <tr>
                             <td colspan="2">
                             <td colspan="2">
                                 <span class="fa fa-chevron-circle-right"></span>
                                 <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>
                             </td>
                         </tr>
                         </tr>
                         <tr><td>&nbsp;</td></tr>
                         <tr><td>&nbsp;</td></tr>
@@ -94,52 +80,15 @@
                         <tr>
                         <tr>
                             <td>
                             <td>
                                 <span class="fa fa-chevron-circle-right"></span>
                                 <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>
                             <td>
                             <td>
+                                <% boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_SECURE));%>
                                 <span class="fa fa-chevron-circle-right"></span>
                                 <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>
                             </td>
                         </tr>
                         </tr>
                     </table>
                     </table>
@@ -158,42 +107,14 @@
                         <b>Proxy/Admin LDAP DN</b>
                         <b>Proxy/Admin LDAP DN</b>
                         <br/>
                         <br/>
                         <span class="fa fa-chevron-circle-right"></span>
                         <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>
                     </div>
                     &nbsp;<br/>
                     &nbsp;<br/>
                     <div class="setting_item">
                     <div class="setting_item">
                         <b>Password</b>
                         <b>Password</b>
                         <br/>
                         <br/>
                         <span class="fa fa-chevron-circle-right"></span>
                         <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>
                 </div>
             </div>
             </div>
@@ -221,22 +142,12 @@
     </div>
     </div>
     <div class="push"></div>
     <div class="push"></div>
 </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>
 <pwm:script>
     <script type="text/javascript">
     <script type="text/javascript">
         function handleFormActivity() {
         function handleFormActivity() {
             require(["dijit/registry"],function(registry){
             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();
                 PWM_GUIDE.updateForm();
                 clearHealthDiv();
                 clearHealthDiv();
             });
             });
@@ -248,17 +159,31 @@
 
 
         PWM_GLOBAL['startupFunctions'].push(function(){
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_VAR['originalHealthBody'] = PWM_MAIN.getObject('healthBody').innerHTML;
             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();
                 clearHealthDiv();
-            });
             checkIfNextEnabled();
             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_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
             PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
             PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
 
 
             PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
             PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
-            PWM_MAIN.addEventHandler('widgetForm','input',function(){handleFormActivity()});
-
         });
         });
 
 
         function checkIfNextEnabled() {
         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="com.novell.ldapchai.cr.Challenge" %>
-<%@ page import="password.pwm.http.bean.ForgottenPasswordBean" %>
+<%@ page import="com.novell.ldapchai.cr.ChallengeSet" %>
 <!DOCTYPE html>
 <!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" %>
 <%@ 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/>">
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ 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
             <% // loop through challenges
                 int counter = 0;
                 int counter = 0;
-                for (final Challenge loopChallenge : recoverBean.getPresentableChallengeSet().getChallenges()) {
+                for (final Challenge loopChallenge : challengeSet.getChallenges()) {
                     counter++;
                     counter++;
             %>
             %>
             <h2><label for="PwmResponse_R_<%=counter%>"><%= loopChallenge.getChallengeText() %>
             <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"/>
                 <input type="hidden" name="processAction" value="checkResponses"/>
                 <button type="submit" name="checkResponses" class="btn" id="submitBtn">
                 <button type="submit" name="checkResponses" class="btn" id="submitBtn">
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
                     <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>
                 </button>
                 <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
                 <% if ("true".equals(JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.ForgottenPasswordOptionalPageView))) { %>
                 <button type="button" id="button-goBack" name="button-goBack" class="btn" >
                 <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) { %>
 <% if (formConfigurationList == null) { %>
 [ form definition is not available ]
 [ form definition is not available ]
 <% } else if (formConfigurationList.isEmpty()) { %>
 <% } else if (formConfigurationList.isEmpty()) { %>
-[ form containes no items ]
+<!-- form contains no items ] -->
 <% } else { %>
 <% } else { %>
 <%
 <%
     final boolean forceReadOnly = (Boolean)JspUtility.getAttribute(pageContext, PwmConstants.REQUEST_ATTR.FormReadOnly);
     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">
               class="pwm-form">
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
             <h2><label for="password"><pwm:display key="Field_Password"/></label></h2>
             <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">
             <div class="buttonbar">
                 <button type="submit" class="btn" name="button" id="submitBtn">
                 <button type="submit" class="btn" name="button" id="submitBtn">
                     <pwm:if test="showIcons"><span class="btn-icon fa fa-sign-in"></span></pwm:if>
                     <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">
     <div id="centerbody">
         <p><pwm:display key="Display_PleaseWaitNewUser"/></p>
         <p><pwm:display key="Display_PleaseWaitNewUser"/></p>
         <%@ include file="fragment/message.jsp" %>
         <%@ 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>
         <div style="text-align: center; width: 100%; padding-top: 50px">
         <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>
 <!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" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<%
-    final PwmRequest pwmRequest = PwmRequest.forRequest(request,response);
-%>
+<% final PwmRequest pwmRequest = PwmRequest.forRequest(request,response); %>
 <html dir="<pwm:LocaleOrientation/>">
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <body class="nihilo">
 <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" %>
         <%@ 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;">
         <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>
                 <tr>
-                    <td style="width:15px">
+                    <td style="width:5%">
                         <span class="fa fa-search"></span>
                         <span class="fa fa-search"></span>
                     </td>
                     </td>
-                    <td style="width:400px">
+                    <td style="width:90%">
                         <input type="search" id="username" name="username" class="peoplesearch-input-username" <pwm:autofocus/> autocomplete="off"/>
                         <input type="search" id="username" name="username" class="peoplesearch-input-username" <pwm:autofocus/> autocomplete="off"/>
                     </td>
                     </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>
                         </div>
                     </td>
                     </td>
                 </tr>
                 </tr>
@@ -80,8 +81,8 @@
                 <span><pwm:display key="Display_JavascriptRequired"/></span>
                 <span><pwm:display key="Display_JavascriptRequired"/></span>
                 <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
                 <a href="<pwm:context/>"><pwm:display key="Title_MainPage"/></a>
             </noscript>
             </noscript>
-            <br/>
         </div>
         </div>
+        <br/>
         <div id="peoplesearch-searchResultsGrid" class="grid">
         <div id="peoplesearch-searchResultsGrid" class="grid">
         </div>
         </div>
     </div>
     </div>

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

@@ -179,17 +179,7 @@
     </filter-mapping>
     </filter-mapping>
     <filter-mapping>
     <filter-mapping>
         <filter-name>SessionFilter</filter-name>
         <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-mapping>
     <filter-mapping>
         <filter-name>CaptchaFilter</filter-name>
         <filter-name>CaptchaFilter</filter-name>

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

@@ -35,7 +35,7 @@
 %>
 %>
 <html dir="<pwm:LocaleOrientation/>">
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="../WEB-INF/jsp/fragment/header.jsp" %>
 <%@ include file="../WEB-INF/jsp/fragment/header.jsp" %>
-<body class="nihilo">
+<body>
 <div id="wrapper">
 <div id="wrapper">
     <jsp:include page="../WEB-INF/jsp/fragment/header-body.jsp">
     <jsp:include page="../WEB-INF/jsp/fragment/header-body.jsp">
         <jsp:param name="pwm.PageName" value="Title_MainPage"/>
         <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_HEADER_WARNINGS); %>
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_THEME); %>
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_THEME); %>
 <% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_HEADER_BUTTONS); %>
 <% 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/>">
 <html dir="<pwm:LocaleOrientation/>">
 <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
 <%@ include file="/WEB-INF/jsp/fragment/header.jsp" %>
 <body class="nihilo">
 <body class="nihilo">
@@ -272,7 +274,6 @@
     });
     });
 </script>
 </script>
 </pwm:script>
 </pwm:script>
-<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_FOOTER_TEXT); %>
 <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %>
 <%@ include file="/WEB-INF/jsp/fragment/footer.jsp" %>
 </body>
 </body>
 </html>
 </html>

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

@@ -156,7 +156,8 @@
 
 
 .btn {
 .btn {
     font-family: Trebuchet MS, sans-serif;
     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:-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% );
     background:linear-gradient(to bottom, #3e374c 5%, #08070f 100% );
 }.btn:hover {
 }.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');
                     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({
             var dropDownButton = new DropDownButton({

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

@@ -1319,7 +1319,6 @@ ActionHandler.defaultValue = {
     ldapMethod:"replace",
     ldapMethod:"replace",
     url:"",
     url:"",
     body:"",
     body:"",
-    bodyEncoding:"url",
     headers:{},
     headers:{},
     attributeName:"",
     attributeName:"",
     attributeValue:""
     attributeValue:""
@@ -1335,10 +1334,6 @@ ActionHandler.ldapMethodOptions = [
     { label: "Add", value: "add" },
     { label: "Add", value: "add" },
     { label: "Remove", value: "remove" }
     { label: "Remove", value: "remove" }
 ];
 ];
-ActionHandler.bodyEncodingOptions = [
-    { label: "None", value: "none" },
-    { label: "URL Encoding", value: "url" }
-];
 
 
 ActionHandler.init = function(keyName) {
 ActionHandler.init = function(keyName) {
     console.log('ActionHandler init for ' + keyName);
     console.log('ActionHandler init for ' + keyName);
@@ -1517,16 +1512,6 @@ ActionHandler.showOptionsDialog = function(keyName, iteration) {
             bodyText += '</tr>';
             bodyText += '</tr>';
             if (PWM_VAR['clientSettingCache'][keyName][iteration]['method'] != 'get') {
             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</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 += '';
             bodyText += '';
         } else if (PWM_VAR['clientSettingCache'][keyName][iteration]['type'] == 'ldap') {
         } 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;
                         PWM_VAR['clientSettingCache'][keyName][iteration]['method'] = value;
                         ActionHandler.writeFormSetting(keyName, function(){ ActionHandler.showOptionsDialog(keyName,iteration)});
                         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_MAIN.addEventHandler('input-' + inputID + '-url','input',function(){
                         PWM_VAR['clientSettingCache'][keyName][iteration]['url'] = PWM_MAIN.getObject('input-' + inputID + '-url').value;
                         PWM_VAR['clientSettingCache'][keyName][iteration]['url'] = PWM_MAIN.getObject('input-' + inputID + '-url').value;
                         ActionHandler.writeFormSetting(keyName);
                         ActionHandler.writeFormSetting(keyName);
@@ -1680,6 +1661,13 @@ ActionHandler.addHeader = function(keyName, iteration) {
 // -------------------------- email table handler ------------------------------------
 // -------------------------- email table handler ------------------------------------
 
 
 var EmailTableHandler = {};
 var EmailTableHandler = {};
+EmailTableHandler.defaultValue = {
+    to:"@User:Email@",
+    from:"@DefaultEmailFromAddress@",
+    subject:"Subject",
+    bodyPlain:"Body",
+    bodyHtml:"Body"
+};
 
 
 EmailTableHandler.init = function(keyName) {
 EmailTableHandler.init = function(keyName) {
     console.log('EmailTableHandler init for ' + 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);
     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"],
     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){
         function(Editor,AlwaysShowToolbar){
             var idValue = keyName + "_" + localeName + "_htmlEditor";
             var idValue = keyName + "_" + localeName + "_htmlEditor";
-            var idValueDialog = idValue + "_Dialog";
             var bodyText = '';
             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({
             PWM_MAIN.showDialog({
                 title: "HTML Editor",
                 title: "HTML Editor",
                 text: bodyText,
                 text: bodyText,
+                showClose:true,
+                showCancel:true,
                 dialogClass: 'wide',
                 dialogClass: 'wide',
                 loadFunction:function(){
                 loadFunction:function(){
                     PWM_MAIN.clearDijitWidget(idValue);
                     PWM_MAIN.clearDijitWidget(idValue);
@@ -1853,27 +1814,30 @@ EmailTableHandler.popupEditor = function(keyName, localeName) {
                             {name:"dijit/_editor/plugins/LinkDialog",command:"createLink",urlRegExp:".*"},
                             {name:"dijit/_editor/plugins/LinkDialog",command:"createLink",urlRegExp:".*"},
                             "fontName","foreColor"
                             "fontName","foreColor"
                         ],
                         ],
-                        height: '',
+                        height: '300px',
                         value: PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'],
                         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();
                     },idValue).startup();
                 },
                 },
                 okAction:function(){
                 okAction:function(){
+                    PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = PWM_VAR['temp-dialogInputValue'];
                     EmailTableHandler.writeSetting(keyName,true);
                     EmailTableHandler.writeSetting(keyName,true);
                 }
                 }
             });
             });
-        });
+        }
+    );
 };
 };
 
 
 
 
 EmailTableHandler.writeSetting = function(settingKey, redraw) {
 EmailTableHandler.writeSetting = function(settingKey, redraw) {
     var currentValues = PWM_VAR['clientSettingCache'][settingKey];
     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 ------------------------------------
 // -------------------------- boolean handler ------------------------------------
@@ -1907,74 +1871,91 @@ BooleanHandler.toggle = function(keyName,widget) {
 // -------------------------- challenge handler ------------------------------------
 // -------------------------- challenge handler ------------------------------------
 
 
 var ChallengeSettingHandler = {};
 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.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 parentDivElement = PWM_MAIN.getObject(parentDiv);
     var bodyText = '';
     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);
     PWM_CFGEDIT.clearDivElements(parentDiv, false);
     for (var localeName in resultValue) {
     for (var localeName in resultValue) {
         (function(localeKey) {
         (function(localeKey) {
-            var isDefaultLocale = localeKey == "";
             var multiValues = resultValue[localeKey];
             var multiValues = resultValue[localeKey];
             var rowCount = PWM_MAIN.itemCount(multiValues);
             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) {
             if (rowCount > 0) {
                 for (var iteration in multiValues) {
                 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>';
                     bodyText += '<div style="text-overflow:ellipsis; white-space:nowrap; overflow:hidden" id="' + id + '">text</div>';
                 }
                 }
             } else {
             } else {
                 bodyText += '[No Questions]';
                 bodyText += '[No Questions]';
             }
             }
             bodyText += '</td></tr>';
             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));
         }(localeName));
     }
     }
-    bodyText += '</table>';
+    parentDivElement.innerHTML = bodyText;
 
 
     var addLocaleFunction = function(localeValue) {
     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.'});
             PWM_MAIN.showDialog({title:PWM_MAIN.showString('Title_Error'),text:'Locale <i>' + localeValue + '</i> is already present.'});
         } else {
         } 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);
     parentDivElement.appendChild(tableElement);
-    UILibrary.addAddLocaleButtonRow(tableElement, keyName, addLocaleFunction, Object.keys(resultValue));
+
+    UILibrary.addAddLocaleButtonRow(tableElement, settingKey, addLocaleFunction, Object.keys(resultValue));
 
 
     for (var localeName in resultValue) {
     for (var localeName in resultValue) {
         (function(localeKey) {
         (function(localeKey) {
@@ -1983,7 +1964,7 @@ ChallengeSettingHandler.draw = function(keyName) {
             if (rowCount > 0) {
             if (rowCount > 0) {
                 for (var iteration in multiValues) {
                 for (var iteration in multiValues) {
                     (function (rowKey) {
                     (function (rowKey) {
-                        var id = 'panel-value-' + keyName + '-' + localeKey + '-' + iteration;
+                        var id = 'panel-value-' + settingKey + '-' + localeKey + '-' + iteration;
                         var questionText = multiValues[rowKey]['text'];
                         var questionText = multiValues[rowKey]['text'];
                         var adminDefined = multiValues[rowKey]['adminDefined'];
                         var adminDefined = multiValues[rowKey]['adminDefined'];
                         var output = (adminDefined ? questionText : '[User Defined]');
                         var output = (adminDefined ? questionText : '[User Defined]');
@@ -1991,6 +1972,10 @@ ChallengeSettingHandler.draw = function(keyName) {
                     }(iteration));
                     }(iteration));
                 }
                 }
             }
             }
+
+            PWM_MAIN.addEventHandler('button-deleteRow-' + settingKey + '-' + localeKey,'click',function(){
+                ChallengeSettingHandler.deleteLocale(settingKey, localeKey)
+            });
         }(localeName));
         }(localeName));
     }
     }
 
 
@@ -2005,43 +1990,28 @@ ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
     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"],
     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){
         function(dojo,registry,dojoParser){
-            var multiValues = resultValue[localeName];
 
 
-            dialogBody += '<table class="noborder">';
+
+            var multiValues = resultValue[localeName];
 
 
             for (var iteration in multiValues) {
             for (var iteration in multiValues) {
                 (function(rowKey) {
                 (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;">';
                     dialogBody += '<td colspan="200" style="border-width: 0;">';
 
 
                     var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
                     var inputID = "value-" + keyName + "-" + localeName + "-" + rowKey;
                     PWM_MAIN.clearDijitWidget(inputID);
                     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 += '</td>';
                     dialogBody += '</tr>';
                     dialogBody += '</tr>';
 
 
                     dialogBody += '<tr style="padding-bottom: 15px; border:0"><td style="padding-bottom: 15px; border:0">';
                     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 += '</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}""';
                     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 += ' onchange="PWM_VAR[\'clientSettingCache\'][\'' + keyName + '\'][\'' + localeKey + '\'][\'' + rowKey + '\'][\'maxQuestionCharsInAnswer\'] = this.value"/><br/> Max Question Chars';
 
 
                     dialogBody += '</td><td style="padding-bottom: 15px; border:0">';
                     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 += '</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));
                 }(iteration));
             }
             }
 
 
-            dialogBody += '</table></div>';
+
+            dialogBody += '</div>';
             dialogBody += '<br/><br/><button type="button" data-dojo-type="dijit/form/Button"';
             dialogBody += '<br/><br/><button type="button" data-dojo-type="dijit/form/Button"';
             dialogBody += ' onclick="ChallengeSettingHandler.addRow(\'' + keyName + '\',\'' + localeKey + '\')"';
             dialogBody += ' onclick="ChallengeSettingHandler.addRow(\'' + keyName + '\',\'' + localeKey + '\')"';
             dialogBody += '><span class="btn-icon fa fa-plus-square"></span>Add Value</button>';
             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;
             var dialogTitle = PWM_SETTINGS['settings'][keyName]['label'] + ' - ' + localeDisplay;
             PWM_MAIN.showDialog({title:dialogTitle,text:dialogBody,showClose:true,dialogClass:'wide',loadFunction:function(){
             PWM_MAIN.showDialog({title:dialogTitle,text:dialogBody,showClose:true,dialogClass:'wide',loadFunction:function(){
                 dojoParser.parse(PWM_MAIN.getObject('challengeLocaleDialogDiv'));
                 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(){
             },okAction:function(){
-                ChallengeSettingHandler.write(keyName );
+                ChallengeSettingHandler.write(keyName);
                 ChallengeSettingHandler.draw(keyName);
                 ChallengeSettingHandler.draw(keyName);
             }});
             }});
         }
         }
@@ -2097,12 +2105,13 @@ ChallengeSettingHandler.deleteLocale = function(keyName,localeKey) {
     PWM_MAIN.showConfirmDialog({
     PWM_MAIN.showConfirmDialog({
         text: 'Are you sure you want to remove all the questions for the <i>' + localeKey + '</i> locale?',
         text: 'Are you sure you want to remove all the questions for the <i>' + localeKey + '</i> locale?',
         okAction:function(){
         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_GUIDE.selectTemplate = function(template) {
     PWM_MAIN.showWaitDialog({title:'Loading...',loadFunction:function() {
     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(){
         PWM_MAIN.preloadAll(function(){
             var url =  "ConfigGuide?processAction=gotoStep&step=" + step;
             var url =  "ConfigGuide?processAction=gotoStep&step=" + step;
             var loadFunction = function(result) {
             var loadFunction = function(result) {
-                if (result['data']) {
+                if (result['error']) {
+                    PWM_MAIN.showErrorDialog(result);
+                    return;
+                } else if (result['data']) {
                     if (result['data']['serverRestart']) {
                     if (result['data']['serverRestart']) {
                         PWM_CONFIG.waitForRestart();
                         PWM_CONFIG.waitForRestart();
                         return;
                         return;

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

@@ -169,11 +169,13 @@ PWM_CONFIG.showHeaderHealth = function() {
                     PWM_MAIN.openHeaderWarningPanel();
                     PWM_MAIN.openHeaderWarningPanel();
                     parentDiv.innerHTML = '<div id="panel-healthHeaderErrors" class="header-error"><span class="fa fa-warning"></span> ' + PWM_ADMIN.showString('Header_ConfigWarningsPresent') + '</div>';
                     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);
                     var tooltipBody = PWM_ADMIN.makeHealthHtml(data['data'],true,false);
+                    /*
                     PWM_MAIN.showTooltip({
                     PWM_MAIN.showTooltip({
                         position:'below',
                         position:'below',
                         id:'panel-healthHeaderErrors',
                         id:'panel-healthHeaderErrors',
                         text:tooltipBody
                         text:tooltipBody
                     });
                     });
+                    */
                 }
                 }
                 setTimeout(function () {
                 setTimeout(function () {
                     PWM_CONFIG.showHeaderHealth()
                     PWM_CONFIG.showHeaderHealth()
@@ -383,12 +385,12 @@ PWM_CONFIG.initConfigHeader = function() {
     // header initialization
     // header initialization
     if (PWM_MAIN.getObject('header_configManagerButton')) {
     if (PWM_MAIN.getObject('header_configManagerButton')) {
         PWM_MAIN.addEventHandler('header_configManagerButton', 'click', function () {
         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')) {
     if (PWM_MAIN.getObject('header_configEditorButton')) {
         PWM_MAIN.addEventHandler('header_configEditorButton', 'click', function () {
         PWM_MAIN.addEventHandler('header_configEditorButton', 'click', function () {
-            PWM_CONFIG.startConfigurationEditor()
+            PWM_CONFIG.startConfigurationEditor();
         });
         });
     }
     }
     PWM_MAIN.addEventHandler('header_openLogViewerButton', 'click', function () {
     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) {
 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) {
 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 preventCache = 'preventCache' in options ? options['preventCache'] : true;
     var addPwmFormID = 'addPwmFormID' in options ? options['addPwmFormID'] : 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 = {};
     var requestHeaders = {};
     requestHeaders['Accept'] = "application/json";
     requestHeaders['Accept'] = "application/json";
     requestHeaders['X-RestClientKey'] = PWM_GLOBAL['restClientKey'];
     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({
     PWM_MAIN.showWaitDialog({
         loadFunction:function(){
         loadFunction:function(){
+            PWM_VAR['detailInProgress'] = false;
             var url = "PeopleSearch?processAction=detail";
             var url = "PeopleSearch?processAction=detail";
             var loadFunction = function(data) {
             var loadFunction = function(data) {
-                PWM_VAR['detailInProgress'] = false;
                 if (data['error'] == true) {
                 if (data['error'] == true) {
                     console.error('unable to load people detail, error: ' + data['errorDetail']);
                     console.error('unable to load people detail, error: ' + data['errorDetail']);
-                    PWM_MAIN.showError(data['errorDetail']);
                     PWM_MAIN.closeWaitDialog();
                     PWM_MAIN.closeWaitDialog();
+                    PWM_MAIN.showErrorDialog(data);
                     return;
                     return;
                 }
                 }
                 var htmlBody = PWM_PS.convertDetailResultToHtml(data['data']);
                 var htmlBody = PWM_PS.convertDetailResultToHtml(data['data']);

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

@@ -32,8 +32,7 @@ html, body {
 #centerbody {
 #centerbody {
     width: auto;
     width: auto;
     min-width: inherit;
     min-width: inherit;
-    padding-left: 3px;
-    padding-right: 3px;
+    padding: 0
 }
 }
 
 
 #centerbody.wide {
 #centerbody.wide {
@@ -45,7 +44,7 @@ html, body {
     width: 100%;
     width: 100%;
     margin: 0;
     margin: 0;
     position:relative;
     position:relative;
-    padding:2px;
+    padding:0;
 }
 }
 
 
 
 
@@ -72,4 +71,31 @@ html, body {
 .dialogBody {
 .dialogBody {
     width: 100%;
     width: 100%;
     max-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;
     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 */
 /* hide recaptcha iframe near footer */
 body > iframe[src="about:blank"] {
 body > iframe[src="about:blank"] {
@@ -776,6 +642,7 @@ dialog {
     animation: fadein 0.3s;
     animation: fadein 0.3s;
     -moz-animation: fadein 0.3s; /* Firefox */
     -moz-animation: fadein 0.3s; /* Firefox */
     -webkit-animation: fadein 0.3s; /* Safari and Chrome */
     -webkit-animation: fadein 0.3s; /* Safari and Chrome */
+    max-width: 100%;
 }
 }
 
 
 dialog .titleBar {
 dialog .titleBar {
@@ -813,4 +680,11 @@ progress:not([value]) {
 .peopleSearch-userDetails {
 .peopleSearch-userDetails {
     max-height: 450px;
     max-height: 450px;
     overflow-y: auto;
     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>
         <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.
             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>
         </td>
     </tr>
     </tr>
 </table>
 </table>