Просмотр исходного кода

Merge branch 'master' into enh-moshi

Jason Rivard 3 лет назад
Родитель
Сommit
6424170918
79 измененных файлов с 1390 добавлено и 1230 удалено
  1. 13 0
      CHANGES.md
  2. 12 0
      build/spotbugs-exclude.xml
  3. 196 494
      client/angular/package-lock.json
  4. 8 8
      client/angular/package.json
  5. 2 2
      data-service/pom.xml
  6. 1 1
      docker/pom.xml
  7. 2 2
      onejar/pom.xml
  8. 11 11
      pom.xml
  9. 2 2
      rest-test-service/pom.xml
  10. 5 5
      server/pom.xml
  11. 1 0
      server/src/main/java/password/pwm/AppProperty.java
  12. 0 4
      server/src/main/java/password/pwm/Permission.java
  13. 0 6
      server/src/main/java/password/pwm/PwmApplication.java
  14. 1 1
      server/src/main/java/password/pwm/PwmDomain.java
  15. 29 12
      server/src/main/java/password/pwm/config/DomainConfig.java
  16. 18 14
      server/src/main/java/password/pwm/config/PwmSetting.java
  17. 3 1
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  18. 15 4
      server/src/main/java/password/pwm/config/profile/ChallengeProfile.java
  19. 32 17
      server/src/main/java/password/pwm/config/profile/ProfileDefinition.java
  20. 49 0
      server/src/main/java/password/pwm/config/profile/SetupResponsesProfile.java
  21. 5 0
      server/src/main/java/password/pwm/error/PwmInternalException.java
  22. 2 2
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  23. 6 0
      server/src/main/java/password/pwm/http/PwmRequest.java
  24. 2 0
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  25. 2 2
      server/src/main/java/password/pwm/http/bean/ActivateUserBean.java
  26. 2 2
      server/src/main/java/password/pwm/http/bean/AdminBean.java
  27. 2 2
      server/src/main/java/password/pwm/http/bean/ChangePasswordBean.java
  28. 2 2
      server/src/main/java/password/pwm/http/bean/ConfigGuideBean.java
  29. 2 2
      server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java
  30. 2 2
      server/src/main/java/password/pwm/http/bean/DeleteAccountBean.java
  31. 2 2
      server/src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  32. 2 2
      server/src/main/java/password/pwm/http/bean/GuestRegistrationBean.java
  33. 2 2
      server/src/main/java/password/pwm/http/bean/LoginServletBean.java
  34. 2 2
      server/src/main/java/password/pwm/http/bean/NewUserBean.java
  35. 2 2
      server/src/main/java/password/pwm/http/bean/PwmSessionBean.java
  36. 2 2
      server/src/main/java/password/pwm/http/bean/SetupOtpBean.java
  37. 8 9
      server/src/main/java/password/pwm/http/bean/SetupResponsesBean.java
  38. 2 2
      server/src/main/java/password/pwm/http/bean/ShortcutsBean.java
  39. 2 2
      server/src/main/java/password/pwm/http/bean/UpdateProfileBean.java
  40. 2 1
      server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  41. 16 12
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  42. 4 3
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  43. 76 70
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  44. 5 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java
  45. 1 1
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  46. 18 33
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  47. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  48. 27 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/ResponseMode.java
  49. 122 359
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java
  50. 314 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java
  51. 33 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/ValidationResponseBean.java
  52. 1 1
      server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java
  53. 18 21
      server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java
  54. 26 6
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  55. 2 0
      server/src/main/java/password/pwm/i18n/Display.java
  56. 2 2
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  57. 2 2
      server/src/main/java/password/pwm/ldap/UserInfoReader.java
  58. 44 26
      server/src/main/java/password/pwm/svc/cr/CrService.java
  59. 18 0
      server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java
  60. 1 1
      server/src/main/java/password/pwm/svc/token/TokenService.java
  61. 1 1
      server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java
  62. 4 1
      server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java
  63. 34 7
      server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java
  64. 10 0
      server/src/main/java/password/pwm/util/secure/SecureEngine.java
  65. 11 11
      server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java
  66. 1 0
      server/src/main/resources/password/pwm/AppProperty.properties
  67. 20 1
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  68. 2 0
      server/src/main/resources/password/pwm/i18n/Display.properties
  69. 10 2
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  70. 1 1
      server/src/test/java/password/pwm/config/stored/StoredConfigKeyTest.java
  71. 1 1
      webapp/pom.xml
  72. 50 3
      webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp
  73. 9 5
      webapp/src/main/webapp/WEB-INF/jsp/fragment/setupresponses-form.jsp
  74. 19 14
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-confirm.jsp
  75. 10 2
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp
  76. 8 4
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp
  77. 2 3
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp
  78. 9 11
      webapp/src/main/webapp/private/index.jsp
  79. 4 1
      webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js

+ 13 - 0
CHANGES.md

@@ -0,0 +1,13 @@
+# Changelog
+Notable changes to PWM will be documented in this file.
+
+
+## [2.1.1] - Unreleased
+### Changed
+- Issue #573 - PWM 5081 at the end of user activation ( no profile assigned )
+- Issue #615 - Error 5203 while editing/removing challenge policy questions in config editor
+
+## [2.0.1] - Unreleased
+### Changed
+- Issue #573 - PWM 5081 at the end of user activation ( no profile assigned )
+- Issue #615 - Error 5203 while editing/removing challenge policy questions in config editor

+ 12 - 0
build/spotbugs-exclude.xml

@@ -31,4 +31,16 @@
         <!-- https://github.com/spotbugs/spotbugs/issues/756 -->
         <!-- https://github.com/spotbugs/spotbugs/issues/756 -->
         <Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
         <Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
     </Match>
     </Match>
+    <Match>
+        <!-- overly aggressive check added in 4.4.1 -->
+        <Bug pattern="MS_EXPOSE_REP"/>
+    </Match>
+    <Match>
+        <!-- overly aggressive check added in 4.4.1 -->
+        <Bug pattern="EI_EXPOSE_REP"/>
+    </Match>
+    <Match>
+        <!-- overly aggressive check added in 4.4.1 -->
+        <Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
 </FindBugsFilter>
 </FindBugsFilter>

Разница между файлами не показана из-за своего большого размера
+ 196 - 494
client/angular/package-lock.json


+ 8 - 8
client/angular/package.json

@@ -20,16 +20,16 @@
         "@microfocus/ias-icons": "1.0.4",
         "@microfocus/ias-icons": "1.0.4",
         "@microfocus/ng-ias": "1.0.1",
         "@microfocus/ng-ias": "1.0.1",
         "@microfocus/ux-ias": "1.1.2",
         "@microfocus/ux-ias": "1.1.2",
-        "@uirouter/angularjs": "1.0.15",
-        "angular": "^1.7.9",
-        "angular-aria": "1.7.8",
-        "angular-sanitize": "1.7.9",
-        "angular-translate": "2.18.1",
-        "core-js": "3.5.0",
+        "@uirouter/angularjs": "1.0.29",
+        "angular": "1.8.2",
+        "angular-aria": "1.8.2",
+        "angular-sanitize": "1.8.2",
+        "angular-translate": "2.19.0",
+        "core-js": "3.18.2",
         "textangular": "1.5.16"
         "textangular": "1.5.16"
     },
     },
     "devDependencies": {
     "devDependencies": {
-        "@types/angular": "^1.8.1",
+        "@types/angular": "^1.8.3",
         "@types/angular-mocks": "1.5.11",
         "@types/angular-mocks": "1.5.11",
         "@types/angular-translate": "2.15.2",
         "@types/angular-translate": "2.15.2",
         "@types/angular-ui-router": "1.1.40",
         "@types/angular-ui-router": "1.1.40",
@@ -46,7 +46,7 @@
         "imports-loader": "0.8.0",
         "imports-loader": "0.8.0",
         "jasmine": "3.2.0",
         "jasmine": "3.2.0",
         "jasmine-core": "3.2.1",
         "jasmine-core": "3.2.1",
-        "jshint": "^2.13.0",
+        "jshint": "^2.13.1",
         "jshint-loader": "0.8.4",
         "jshint-loader": "0.8.4",
         "json-loader": "0.5.7",
         "json-loader": "0.5.7",
         "karma": "^4.4.1",
         "karma": "^4.4.1",

+ 2 - 2
data-service/pom.xml

@@ -46,7 +46,7 @@
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
                 <artifactId>maven-war-plugin</artifactId>
-                <version>3.3.1</version>
+                <version>3.3.2</version>
                 <configuration>
                 <configuration>
                     <archiveClasses>true</archiveClasses>
                     <archiveClasses>true</archiveClasses>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
@@ -155,7 +155,7 @@
         <dependency>
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
             <artifactId>gson</artifactId>
-            <version>2.8.7</version>
+            <version>2.8.9</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
             <groupId>org.jetbrains.xodus</groupId>

+ 1 - 1
docker/pom.xml

@@ -33,7 +33,7 @@
             <plugin>
             <plugin>
                 <groupId>com.google.cloud.tools</groupId>
                 <groupId>com.google.cloud.tools</groupId>
                 <artifactId>jib-maven-plugin</artifactId>
                 <artifactId>jib-maven-plugin</artifactId>
-                <version>3.1.2</version>
+                <version>3.1.4</version>
                 <executions>
                 <executions>
                     <execution>
                     <execution>
                         <id>make-docker-image</id>
                         <id>make-docker-image</id>

+ 2 - 2
onejar/pom.xml

@@ -16,7 +16,7 @@
     <name>PWM Password Self Service: Executable Server JAR</name>
     <name>PWM Password Self Service: Executable Server JAR</name>
 
 
     <properties>
     <properties>
-        <tomcat.version>9.0.52</tomcat.version>
+        <tomcat.version>9.0.55</tomcat.version>
     </properties>
     </properties>
 
 
     <build>
     <build>
@@ -109,7 +109,7 @@
         <dependency>
         <dependency>
             <groupId>commons-cli</groupId>
             <groupId>commons-cli</groupId>
             <artifactId>commons-cli</artifactId>
             <artifactId>commons-cli</artifactId>
-            <version>1.4</version>
+            <version>1.5.0</version>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>
 </project>
 </project>

+ 11 - 11
pom.xml

@@ -96,7 +96,7 @@
             <plugin>
             <plugin>
                 <groupId>org.commonjava.maven.plugins</groupId>
                 <groupId>org.commonjava.maven.plugins</groupId>
                 <artifactId>directory-maven-plugin</artifactId>
                 <artifactId>directory-maven-plugin</artifactId>
-                <version>0.3.1</version>
+                <version>1.0</version>
                 <executions>
                 <executions>
                     <execution>
                     <execution>
                         <id>project-root-directory</id>
                         <id>project-root-directory</id>
@@ -147,7 +147,7 @@
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.3.0</version>
+                <version>3.3.1</version>
                 <executions>
                 <executions>
                     <execution>
                     <execution>
                         <goals>
                         <goals>
@@ -252,7 +252,7 @@
                     <dependency>
                     <dependency>
                         <groupId>com.puppycrawl.tools</groupId>
                         <groupId>com.puppycrawl.tools</groupId>
                         <artifactId>checkstyle</artifactId>
                         <artifactId>checkstyle</artifactId>
-                        <version>8.45.1</version>
+                        <version>9.1</version>
                     </dependency>
                     </dependency>
                 </dependencies>
                 </dependencies>
                 <executions>
                 <executions>
@@ -331,12 +331,12 @@
             <plugin>
             <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
-                <version>4.2.3</version>
+                <version>4.4.2.2</version>
                 <dependencies>
                 <dependencies>
                     <dependency>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
                         <groupId>com.github.spotbugs</groupId>
                         <artifactId>spotbugs</artifactId>
                         <artifactId>spotbugs</artifactId>
-                        <version>4.2.3</version>
+                        <version>4.5.0</version>
                     </dependency>
                     </dependency>
                 </dependencies>
                 </dependencies>
                 <configuration>
                 <configuration>
@@ -385,7 +385,7 @@
             <plugin>
             <plugin>
                 <groupId>org.owasp</groupId>
                 <groupId>org.owasp</groupId>
                 <artifactId>dependency-check-maven</artifactId>
                 <artifactId>dependency-check-maven</artifactId>
-                <version>6.2.2</version>
+                <version>6.4.1</version>
                 <executions>
                 <executions>
                     <execution>
                     <execution>
                         <goals>
                         <goals>
@@ -434,13 +434,13 @@
         <dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <artifactId>lombok</artifactId>
-            <version>1.18.20</version>
+            <version>1.18.22</version>
             <scope>provided</scope>
             <scope>provided</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
             <artifactId>spotbugs-annotations</artifactId>
-            <version>4.2.3</version>
+            <version>4.5.0</version>
             <scope>provided</scope>
             <scope>provided</scope>
         </dependency>
         </dependency>
 
 
@@ -454,13 +454,13 @@
         <dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <artifactId>mockito-core</artifactId>
-            <version>3.11.2</version>
+            <version>4.0.0</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
             <artifactId>assertj-core</artifactId>
-            <version>3.20.2</version>
+            <version>3.21.0</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
@@ -472,7 +472,7 @@
         <dependency>
         <dependency>
             <groupId>org.reflections</groupId>
             <groupId>org.reflections</groupId>
             <artifactId>reflections</artifactId>
             <artifactId>reflections</artifactId>
-            <version>0.9.12</version>
+            <version>0.10.2</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>

+ 2 - 2
rest-test-service/pom.xml

@@ -28,7 +28,7 @@
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
                 <artifactId>maven-war-plugin</artifactId>
-                <version>3.3.1</version>
+                <version>3.3.2</version>
                 <configuration>
                 <configuration>
                     <archiveClasses>true</archiveClasses>
                     <archiveClasses>true</archiveClasses>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
                     <packagingExcludes>WEB-INF/classes</packagingExcludes>
@@ -79,7 +79,7 @@
         <dependency>
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
             <artifactId>gson</artifactId>
-            <version>2.8.7</version>
+            <version>2.8.9</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <groupId>org.apache.commons</groupId>

+ 5 - 5
server/pom.xml

@@ -309,7 +309,7 @@
         <dependency>
         <dependency>
             <groupId>com.google.code.gson</groupId>
             <groupId>com.google.code.gson</groupId>
             <artifactId>gson</artifactId>
             <artifactId>gson</artifactId>
-            <version>2.8.7</version>
+            <version>2.8.9</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.squareup.moshi</groupId>
             <groupId>com.squareup.moshi</groupId>
@@ -319,7 +319,7 @@
         <dependency>
         <dependency>
             <groupId>com.blueconic</groupId>
             <groupId>com.blueconic</groupId>
             <artifactId>browscap-java</artifactId>
             <artifactId>browscap-java</artifactId>
-            <version>1.3.6</version>
+            <version>1.3.7</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
             <groupId>org.jetbrains.xodus</groupId>
@@ -330,17 +330,17 @@
         <dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-nop</artifactId>
             <artifactId>slf4j-nop</artifactId>
-            <version>1.7.22</version>
+            <version>2.0.0-alpha5</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.webjars</groupId>
             <groupId>org.webjars</groupId>
             <artifactId>webjars-locator-core</artifactId>
             <artifactId>webjars-locator-core</artifactId>
-            <version>0.47</version>
+            <version>0.48</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
             <artifactId>caffeine</artifactId>
-            <version>3.0.3</version>
+            <version>3.0.4</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.nulab-inc</groupId>
             <groupId>com.nulab-inc</groupId>

+ 1 - 0
server/src/main/java/password/pwm/AppProperty.java

@@ -346,6 +346,7 @@ public enum AppProperty
     SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH         ( "security.config.minSecurityKeyLength" ),
     SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH         ( "security.config.minSecurityKeyLength" ),
     SECURITY_DEFAULT_EPHEMERAL_BLOCK_ALG            ( "security.defaultEphemeralBlockAlg" ),
     SECURITY_DEFAULT_EPHEMERAL_BLOCK_ALG            ( "security.defaultEphemeralBlockAlg" ),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ( "security.defaultEphemeralHashAlg" ),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ( "security.defaultEphemeralHashAlg" ),
+    SECURITY_DEFAULT_EPHEMERAL_HMAC_ALG             ( "security.defaultEphemeralHmacAlg" ),
     SEEDLIST_BUILTIN_PATH                           ( "seedlist.builtin.path" ),
     SEEDLIST_BUILTIN_PATH                           ( "seedlist.builtin.path" ),
     SMTP_IO_CONNECT_TIMEOUT                         ( "smtp.io.connectTimeoutMs" ),
     SMTP_IO_CONNECT_TIMEOUT                         ( "smtp.io.connectTimeoutMs" ),
     SMTP_IO_READ_TIMEOUT                            ( "smtp.io.readTimeoutMs" ),
     SMTP_IO_READ_TIMEOUT                            ( "smtp.io.readTimeoutMs" ),

+ 0 - 4
server/src/main/java/password/pwm/Permission.java

@@ -28,11 +28,7 @@ import password.pwm.config.PwmSetting;
 public enum Permission
 public enum Permission
 {
 {
     PWMADMIN( PwmSetting.QUERY_MATCH_PWM_ADMIN ),
     PWMADMIN( PwmSetting.QUERY_MATCH_PWM_ADMIN ),
-    SETUP_RESPONSE( PwmSetting.QUERY_MATCH_SETUP_RESPONSE ),
-    SETUP_OTP_SECRET( PwmSetting.OTP_SETUP_USER_PERMISSION ),
     GUEST_REGISTRATION( PwmSetting.GUEST_ADMIN_GROUP ),
     GUEST_REGISTRATION( PwmSetting.GUEST_ADMIN_GROUP ),
-    PEOPLE_SEARCH( PwmSetting.PEOPLE_SEARCH_QUERY_MATCH ),
-    PROFILE_UPDATE( PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
     WEBSERVICE( PwmSetting.WEBSERVICES_QUERY_MATCH ),
     WEBSERVICE( PwmSetting.WEBSERVICES_QUERY_MATCH ),
     WEBSERVICE_THIRDPARTY( PwmSetting.WEBSERVICES_THIRDPARTY_QUERY_MATCH ),;
     WEBSERVICE_THIRDPARTY( PwmSetting.WEBSERVICES_THIRDPARTY_QUERY_MATCH ),;
 
 

+ 0 - 6
server/src/main/java/password/pwm/PwmApplication.java

@@ -59,7 +59,6 @@ import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsService;
 import password.pwm.svc.stats.StatisticsService;
-import password.pwm.svc.token.TokenService;
 import password.pwm.svc.wordlist.SeedlistService;
 import password.pwm.svc.wordlist.SeedlistService;
 import password.pwm.svc.wordlist.SharedHistoryService;
 import password.pwm.svc.wordlist.SharedHistoryService;
 import password.pwm.svc.wordlist.WordlistService;
 import password.pwm.svc.wordlist.WordlistService;
@@ -876,11 +875,6 @@ public class PwmApplication
         return lastLocalDBFailure;
         return lastLocalDBFailure;
     }
     }
 
 
-    public TokenService getTokenService( )
-    {
-        return ( TokenService ) pwmServiceManager.getService( PwmServiceEnum.TokenService );
-    }
-
     public SessionTrackService getSessionTrackService( )
     public SessionTrackService getSessionTrackService( )
     {
     {
         return ( SessionTrackService ) pwmServiceManager.getService( PwmServiceEnum.SessionTrackService );
         return ( SessionTrackService ) pwmServiceManager.getService( PwmServiceEnum.SessionTrackService );

+ 1 - 1
server/src/main/java/password/pwm/PwmDomain.java

@@ -209,7 +209,7 @@ public class PwmDomain
 
 
     public TokenService getTokenService()
     public TokenService getTokenService()
     {
     {
-        return pwmApplication.getTokenService();
+        return ( TokenService ) pwmServiceManager.getService( PwmServiceEnum.TokenService );
     }
     }
 
 
     public SharedHistoryService getSharedHistoryManager()
     public SharedHistoryService getSharedHistoryManager()

+ 29 - 12
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -38,6 +38,7 @@ import password.pwm.config.profile.Profile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.SetupOtpProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.stored.StoredConfigurationUtil;
@@ -62,7 +63,6 @@ import java.io.StringWriter;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
@@ -85,7 +85,8 @@ public class DomainConfig implements SettingReader
 
 
     private final ConfigurationSuppliers configurationSuppliers = new ConfigurationSuppliers();
     private final ConfigurationSuppliers configurationSuppliers = new ConfigurationSuppliers();
 
 
-    private final DataCache dataCache = new DataCache();
+    private final Map<String, PwmPasswordPolicy> cachedPasswordPolicy;
+    private final Map<String, Map<Locale, ChallengeProfile>> cachedChallengeProfiles;
     private final StoredSettingReader settingReader;
     private final StoredSettingReader settingReader;
 
 
     public DomainConfig( final AppConfig appConfig, final DomainID domainID )
     public DomainConfig( final AppConfig appConfig, final DomainID domainID )
@@ -94,8 +95,25 @@ public class DomainConfig implements SettingReader
         this.storedConfiguration = appConfig.getStoredConfiguration();
         this.storedConfiguration = appConfig.getStoredConfiguration();
         this.domainID = Objects.requireNonNull( domainID );
         this.domainID = Objects.requireNonNull( domainID );
         this.settingReader = new StoredSettingReader( storedConfiguration, null, domainID );
         this.settingReader = new StoredSettingReader( storedConfiguration, null, domainID );
-    }
 
 
+        this.cachedPasswordPolicy = Map.copyOf( getPasswordProfileIDs().stream()
+                .map( profile -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile ) )
+                .collect( Collectors.toMap(
+                        PwmPasswordPolicy::getIdentifier,
+                        pwmPasswordPolicy -> pwmPasswordPolicy
+                ) ) );
+
+        this.cachedChallengeProfiles = Map.copyOf( getChallengeProfileIDs().stream()
+                .collect( Collectors.toMap(
+                        profileId -> profileId,
+                        profileId -> Map.copyOf( appConfig.getKnownLocales().stream()
+                                .collect( Collectors.toMap(
+                                        locale -> locale,
+                                        locale -> ChallengeProfile.readChallengeProfileFromConfig( domainID, profileId, locale, storedConfiguration )
+                                ) ) )
+                ) ) );
+    }
+    
     public AppConfig getAppConfig()
     public AppConfig getAppConfig()
     {
     {
         return appConfig;
         return appConfig;
@@ -184,7 +202,7 @@ public class DomainConfig implements SettingReader
             throw new IllegalArgumentException( "unknown challenge profileID specified: " + profile );
             throw new IllegalArgumentException( "unknown challenge profileID specified: " + profile );
         }
         }
 
 
-        return ChallengeProfile.readChallengeProfileFromConfig( getDomainID(), profile, locale, storedConfiguration );
+        return cachedChallengeProfiles.get( profile ).get( locale );
     }
     }
 
 
     public long readSettingAsLong( final PwmSetting setting )
     public long readSettingAsLong( final PwmSetting setting )
@@ -194,8 +212,7 @@ public class DomainConfig implements SettingReader
 
 
     public PwmPasswordPolicy getPasswordPolicy( final String profile )
     public PwmPasswordPolicy getPasswordPolicy( final String profile )
     {
     {
-        return dataCache.cachedPasswordPolicy
-                .computeIfAbsent( profile, s -> PwmPasswordPolicy.createPwmPasswordPolicy( this, profile ) );
+        return cachedPasswordPolicy.get( profile );
     }
     }
 
 
     public List<String> getPasswordProfileIDs( )
     public List<String> getPasswordProfileIDs( )
@@ -263,7 +280,7 @@ public class DomainConfig implements SettingReader
     {
     {
         return appConfig.readAppProperty( property );
         return appConfig.readAppProperty( property );
     }
     }
-    
+
     public DomainID getDomainID()
     public DomainID getDomainID()
     {
     {
         return domainID;
         return domainID;
@@ -302,11 +319,6 @@ public class DomainConfig implements SettingReader
         } );
         } );
     }
     }
 
 
-    private static class DataCache
-    {
-        private final Map<String, PwmPasswordPolicy> cachedPasswordPolicy = new LinkedHashMap<>();
-    }
-
     /* generic profile stuff */
     /* generic profile stuff */
     public Map<String, NewUserProfile> getNewUserProfiles( )
     public Map<String, NewUserProfile> getNewUserProfiles( )
     {
     {
@@ -333,6 +345,11 @@ public class DomainConfig implements SettingReader
         return this.getProfileMap( ProfileDefinition.SetupOTPProfile );
         return this.getProfileMap( ProfileDefinition.SetupOTPProfile );
     }
     }
 
 
+    public Map<String, SetupResponsesProfile> getSetupResponseProfiles( )
+    {
+        return this.getProfileMap( ProfileDefinition.SetupResponsesProfile );
+    }
+
     public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
     public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
     {
     {
         return this.getProfileMap( ProfileDefinition.UpdateAttributes );
         return this.getProfileMap( ProfileDefinition.UpdateAttributes );

+ 18 - 14
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -664,8 +664,6 @@ public enum PwmSetting
             "otp.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
             "otp.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     OTP_SETUP_USER_PERMISSION(
     OTP_SETUP_USER_PERMISSION(
             "otp.secret.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.OTP_PROFILE ),
             "otp.secret.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.OTP_PROFILE ),
-    OTP_ALLOW_SETUP(
-            "otp.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.OTP_PROFILE ),
     OTP_FORCE_SETUP(
     OTP_FORCE_SETUP(
             "otp.forceSetup", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_PROFILE ),
             "otp.forceSetup", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_PROFILE ),
     OTP_SECRET_IDENTIFIER(
     OTP_SECRET_IDENTIFIER(
@@ -673,6 +671,8 @@ public enum PwmSetting
     OTP_RECOVERY_CODES(
     OTP_RECOVERY_CODES(
             "otp.secret.recoveryCodes", PwmSettingSyntax.NUMERIC, PwmSettingCategory.OTP_PROFILE ),
             "otp.secret.recoveryCodes", PwmSettingSyntax.NUMERIC, PwmSettingCategory.OTP_PROFILE ),
 
 
+    OTP_ALLOW_SETUP(
+            "otp.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.OTP_SETTINGS ),
     OTP_SECRET_READ_PREFERENCE(
     OTP_SECRET_READ_PREFERENCE(
             "otp.secret.readPreference", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_SETTINGS ),
             "otp.secret.readPreference", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_SETTINGS ),
     OTP_SECRET_WRITE_PREFERENCE(
     OTP_SECRET_WRITE_PREFERENCE(
@@ -732,20 +732,24 @@ public enum PwmSetting
             "audit.syslog.outputFormat", PwmSettingSyntax.SELECT, PwmSettingCategory.AUDIT_FORWARD ),
             "audit.syslog.outputFormat", PwmSettingSyntax.SELECT, PwmSettingCategory.AUDIT_FORWARD ),
 
 
     // challenge settings
     // challenge settings
-    CHALLENGE_ENABLE(
-            "challenge.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHALLENGE ),
-    CHALLENGE_FORCE_SETUP(
-            "challenge.forceSetup", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHALLENGE ),
-    CHALLENGE_SHOW_CONFIRMATION(
-            "challenge.showConfirmation", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHALLENGE ),
-    CHALLENGE_CASE_INSENSITIVE(
-            "challenge.caseInsensitive", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHALLENGE ),
-    CHALLENGE_ALLOW_DUPLICATE_RESPONSES(
-            "challenge.allowDuplicateResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHALLENGE ),
+    SETUP_RESPONSE_ENABLE(
+            "challenge.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_SETTINGS ),
+    SETUP_RESPONSES_CASE_INSENSITIVE(
+            "challenge.caseInsensitive", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_SETTINGS ),
+    SETUP_RESPONSES_ALLOW_DUPLICATE_RESPONSES(
+            "challenge.allowDuplicateResponses", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_SETTINGS ),
+    SETUP_RESPONSE_PROFILE_LIST(
+            "setupResponses.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
+    SETUP_RESPONSES_FORCE_SETUP(
+            "challenge.forceSetup", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
+    SETUP_RESPONSES_HELPDESK_FORCE_SETUP(
+            "challenge.helpdesk.forceSetup", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
+    SETUP_RESPONSES_SHOW_CONFIRMATION(
+            "challenge.showConfirmation", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
     QUERY_MATCH_SETUP_RESPONSE(
     QUERY_MATCH_SETUP_RESPONSE(
-            "challenge.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.CHALLENGE ),
+            "challenge.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
     QUERY_MATCH_CHECK_RESPONSES(
     QUERY_MATCH_CHECK_RESPONSES(
-            "command.checkResponses.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.CHALLENGE ),
+            "command.checkResponses.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
 
 
     // challenge policy profile
     // challenge policy profile
     CHALLENGE_PROFILE_LIST(
     CHALLENGE_PROFILE_LIST(

+ 3 - 1
server/src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -154,7 +154,9 @@ public enum PwmSettingCategory
     CHANGE_PASSWORD_SETTINGS( CHANGE_PASSWORD ),
     CHANGE_PASSWORD_SETTINGS( CHANGE_PASSWORD ),
     CHANGE_PASSWORD_PROFILE( CHANGE_PASSWORD ),
     CHANGE_PASSWORD_PROFILE( CHANGE_PASSWORD ),
 
 
-    CHALLENGE( MODULES_PRIVATE ),
+    SETUP_RESPONSES( MODULES_PRIVATE ),
+    SETUP_RESPONSES_SETTINGS( SETUP_RESPONSES ),
+    SETUP_RESPONSES_PROFILE( SETUP_RESPONSES ),
 
 
     RECOVERY( MODULES_PUBLIC ),
     RECOVERY( MODULES_PUBLIC ),
     RECOVERY_SETTINGS( RECOVERY ),
     RECOVERY_SETTINGS( RECOVERY ),

+ 15 - 4
server/src/main/java/password/pwm/config/profile/ChallengeProfile.java

@@ -42,6 +42,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
+import java.util.Optional;
 
 
 public class ChallengeProfile implements Profile, Serializable
 public class ChallengeProfile implements Profile, Serializable
 {
 {
@@ -154,14 +155,14 @@ public class ChallengeProfile implements Profile, Serializable
         return locale;
         return locale;
     }
     }
 
 
-    public ChallengeSet getChallengeSet( )
+    public Optional<ChallengeSet> getChallengeSet( )
     {
     {
-        return challengeSet;
+        return Optional.ofNullable( challengeSet );
     }
     }
 
 
-    public ChallengeSet getHelpdeskChallengeSet( )
+    public Optional<ChallengeSet> getHelpdeskChallengeSet( )
     {
     {
-        return helpdeskChallengeSet;
+        return Optional.ofNullable( helpdeskChallengeSet );
     }
     }
 
 
     public int getMinRandomSetup( )
     public int getMinRandomSetup( )
@@ -265,4 +266,14 @@ public class ChallengeProfile implements Profile, Serializable
     {
     {
         throw new UnsupportedOperationException();
         throw new UnsupportedOperationException();
     }
     }
+
+    public boolean hasChallenges()
+    {
+        return !getChallengeSet().map( ChallengeSet::getChallenges ).orElse( Collections.emptyList() ).isEmpty();
+    }
+
+    public boolean hasHelpdeskChallenges()
+    {
+        return !getHelpdeskChallengeSet().map( ChallengeSet::getChallenges ).orElse( Collections.emptyList() ).isEmpty();
+    }
 }
 }

+ 32 - 17
server/src/main/java/password/pwm/config/profile/ProfileDefinition.java

@@ -32,97 +32,106 @@ public enum ProfileDefinition
             ChangePasswordProfile.class,
             ChangePasswordProfile.class,
             ChangePasswordProfile.ChangePasswordProfileFactory.class,
             ChangePasswordProfile.ChangePasswordProfileFactory.class,
             PwmSettingCategory.CHANGE_PASSWORD_PROFILE,
             PwmSettingCategory.CHANGE_PASSWORD_PROFILE,
-            PwmSetting.QUERY_MATCH_CHANGE_PASSWORD ),
+            PwmSetting.QUERY_MATCH_CHANGE_PASSWORD, null ),
     AccountInformation(
     AccountInformation(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             AccountInformationProfile.class,
             AccountInformationProfile.class,
             AccountInformationProfile.AccountInformationProfileFactory.class,
             AccountInformationProfile.AccountInformationProfileFactory.class,
             PwmSettingCategory.ACCOUNT_INFO_PROFILE,
             PwmSettingCategory.ACCOUNT_INFO_PROFILE,
-            PwmSetting.ACCOUNT_INFORMATION_QUERY_MATCH ),
+            PwmSetting.ACCOUNT_INFORMATION_QUERY_MATCH, null ),
     Helpdesk(
     Helpdesk(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             HelpdeskProfile.class,
             HelpdeskProfile.class,
             HelpdeskProfile.HelpdeskProfileFactory.class,
             HelpdeskProfile.HelpdeskProfileFactory.class,
             PwmSettingCategory.HELPDESK_PROFILE,
             PwmSettingCategory.HELPDESK_PROFILE,
-            PwmSetting.HELPDESK_PROFILE_QUERY_MATCH ),
+            PwmSetting.HELPDESK_PROFILE_QUERY_MATCH, null ),
     ForgottenPassword(
     ForgottenPassword(
             Type.PUBLIC,
             Type.PUBLIC,
             ForgottenPasswordProfile.class,
             ForgottenPasswordProfile.class,
             ForgottenPasswordProfile.ForgottenPasswordProfileFactory.class,
             ForgottenPasswordProfile.ForgottenPasswordProfileFactory.class,
             PwmSettingCategory.RECOVERY_PROFILE,
             PwmSettingCategory.RECOVERY_PROFILE,
-            PwmSetting.RECOVERY_PROFILE_QUERY_MATCH ),
+            PwmSetting.RECOVERY_PROFILE_QUERY_MATCH, null ),
     NewUser(
     NewUser(
             Type.PUBLIC,
             Type.PUBLIC,
             NewUserProfile.class,
             NewUserProfile.class,
             NewUserProfile.NewUserProfileFactory.class,
             NewUserProfile.NewUserProfileFactory.class,
             PwmSettingCategory.NEWUSER_PROFILE,
             PwmSettingCategory.NEWUSER_PROFILE,
-            null ),
+            null, null ),
     UpdateAttributes(
     UpdateAttributes(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             UpdateProfileProfile.class,
             UpdateProfileProfile.class,
             UpdateProfileProfile.UpdateProfileProfileFactory.class,
             UpdateProfileProfile.UpdateProfileProfileFactory.class,
             PwmSettingCategory.UPDATE_PROFILE,
             PwmSettingCategory.UPDATE_PROFILE,
-            PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
+            PwmSetting.UPDATE_PROFILE_QUERY_MATCH, null ),
     ActivateUser(
     ActivateUser(
             Type.PUBLIC,
             Type.PUBLIC,
             ActivateUserProfile.class,
             ActivateUserProfile.class,
             ActivateUserProfile.UserActivationProfileFactory.class,
             ActivateUserProfile.UserActivationProfileFactory.class,
             PwmSettingCategory.ACTIVATION_PROFILE,
             PwmSettingCategory.ACTIVATION_PROFILE,
-            PwmSetting.ACTIVATE_USER_QUERY_MATCH ),
+            PwmSetting.ACTIVATE_USER_QUERY_MATCH, null ),
     DeleteAccount(
     DeleteAccount(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             DeleteAccountProfile.class,
             DeleteAccountProfile.class,
             DeleteAccountProfile.DeleteAccountProfileFactory.class,
             DeleteAccountProfile.DeleteAccountProfileFactory.class,
             PwmSettingCategory.DELETE_ACCOUNT_PROFILE,
             PwmSettingCategory.DELETE_ACCOUNT_PROFILE,
-            PwmSetting.DELETE_ACCOUNT_PERMISSION ),
+            PwmSetting.DELETE_ACCOUNT_PERMISSION, null ),
+    SetupResponsesProfile(
+            Type.AUTHENTICATED,
+            SetupResponsesProfile.class,
+            password.pwm.config.profile.SetupResponsesProfile.SetupResponseProfileFactory.class,
+            PwmSettingCategory.SETUP_RESPONSES_PROFILE,
+            PwmSetting.QUERY_MATCH_SETUP_RESPONSE,
+            PwmSetting.SETUP_RESPONSE_ENABLE ),
     SetupOTPProfile(
     SetupOTPProfile(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             SetupOtpProfile.class,
             SetupOtpProfile.class,
             SetupOtpProfile.SetupOtpProfileFactory.class,
             SetupOtpProfile.SetupOtpProfileFactory.class,
             PwmSettingCategory.OTP_PROFILE,
             PwmSettingCategory.OTP_PROFILE,
-            PwmSetting.OTP_SETUP_USER_PERMISSION ),
+            PwmSetting.OTP_SETUP_USER_PERMISSION,
+            PwmSetting.OTP_ALLOW_SETUP ),
     PeopleSearch(
     PeopleSearch(
             Type.AUTHENTICATED,
             Type.AUTHENTICATED,
             PeopleSearchProfile.class,
             PeopleSearchProfile.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
-            PwmSetting.PEOPLE_SEARCH_QUERY_MATCH ),
+            PwmSetting.PEOPLE_SEARCH_QUERY_MATCH, null ),
     PeopleSearchPublic(
     PeopleSearchPublic(
             Type.PUBLIC,
             Type.PUBLIC,
             PeopleSearchProfile.class,
             PeopleSearchProfile.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
-            null ),
+            null, null ),
     EmailServers(
     EmailServers(
             Type.SERVICE,
             Type.SERVICE,
             EmailServerProfile.class,
             EmailServerProfile.class,
             EmailServerProfile.EmailServerProfileFactory.class,
             EmailServerProfile.EmailServerProfileFactory.class,
             PwmSettingCategory.EMAIL_SERVERS,
             PwmSettingCategory.EMAIL_SERVERS,
-            null ),
+            null, null ),
     PasswordPolicy(
     PasswordPolicy(
             Type.SERVICE,
             Type.SERVICE,
             PwmPasswordPolicy.class,
             PwmPasswordPolicy.class,
             null,
             null,
             PwmSettingCategory.PASSWORD_POLICY,
             PwmSettingCategory.PASSWORD_POLICY,
-            PwmSetting.PASSWORD_POLICY_QUERY_MATCH ),
+            PwmSetting.PASSWORD_POLICY_QUERY_MATCH, null ),
     LdapProfile(
     LdapProfile(
             Type.SERVICE,
             Type.SERVICE,
             LdapProfile.class,
             LdapProfile.class,
             LdapProfile.LdapProfileFactory.class,
             LdapProfile.LdapProfileFactory.class,
             PwmSettingCategory.LDAP_PROFILE,
             PwmSettingCategory.LDAP_PROFILE,
-            null ),
+            null, null ),
     ChallengeProfile(
     ChallengeProfile(
             Type.SERVICE,
             Type.SERVICE,
             ChallengeProfile.class,
             ChallengeProfile.class,
             null,
             null,
             PwmSettingCategory.CHALLENGE_POLICY,
             PwmSettingCategory.CHALLENGE_POLICY,
-            PwmSetting.CHALLENGE_POLICY_QUERY_MATCH ),;
+            PwmSetting.CHALLENGE_POLICY_QUERY_MATCH, null ),;
 
 
     private final Type type;
     private final Type type;
     private final Class<? extends Profile> profileImplClass;
     private final Class<? extends Profile> profileImplClass;
     private final Class<? extends Profile.ProfileFactory> profileFactoryClass;
     private final Class<? extends Profile.ProfileFactory> profileFactoryClass;
     private final PwmSettingCategory category;
     private final PwmSettingCategory category;
     private final PwmSetting queryMatch;
     private final PwmSetting queryMatch;
+    private final PwmSetting enabledSetting;
 
 
     enum Type
     enum Type
     {
     {
@@ -136,14 +145,15 @@ public enum ProfileDefinition
             final Class<? extends Profile> profileImplClass,
             final Class<? extends Profile> profileImplClass,
             final Class<? extends Profile.ProfileFactory> profileFactoryClass,
             final Class<? extends Profile.ProfileFactory> profileFactoryClass,
             final PwmSettingCategory category,
             final PwmSettingCategory category,
-            final PwmSetting queryMatch
-    )
+            final PwmSetting queryMatch,
+            final PwmSetting enabledSetting )
     {
     {
         this.type = type;
         this.type = type;
         this.profileImplClass = profileImplClass;
         this.profileImplClass = profileImplClass;
         this.profileFactoryClass = profileFactoryClass;
         this.profileFactoryClass = profileFactoryClass;
         this.category = category;
         this.category = category;
         this.queryMatch = queryMatch;
         this.queryMatch = queryMatch;
+        this.enabledSetting = enabledSetting;
     }
     }
 
 
     public boolean isAuthenticated( )
     public boolean isAuthenticated( )
@@ -170,4 +180,9 @@ public enum ProfileDefinition
     {
     {
         return Optional.ofNullable( profileFactoryClass );
         return Optional.ofNullable( profileFactoryClass );
     }
     }
+
+    public Optional<PwmSetting> getEnabledSetting()
+    {
+        return Optional.ofNullable( enabledSetting );
+    }
 }
 }

+ 49 - 0
server/src/main/java/password/pwm/config/profile/SetupResponsesProfile.java

@@ -0,0 +1,49 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.config.profile;
+
+import password.pwm.bean.DomainID;
+import password.pwm.config.stored.StoredConfiguration;
+
+public class SetupResponsesProfile extends AbstractProfile
+{
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.SetupOTPProfile;
+
+    protected SetupResponsesProfile( final DomainID domainID, final String identifier, final StoredConfiguration storedConfiguration )
+    {
+        super( domainID, identifier, storedConfiguration );
+    }
+
+    @Override
+    public ProfileDefinition profileType( )
+    {
+        return PROFILE_TYPE;
+    }
+
+    public static class SetupResponseProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final DomainID domainID,  final String identifier )
+        {
+            return new SetupResponsesProfile( domainID, identifier, storedConfiguration );
+        }
+    }
+}

+ 5 - 0
server/src/main/java/password/pwm/error/PwmInternalException.java

@@ -26,4 +26,9 @@ public class PwmInternalException extends RuntimeException
     {
     {
         super( message, cause );
         super( message, cause );
     }
     }
+
+    public PwmInternalException( final String message )
+    {
+        super( message );
+    }
 }
 }

+ 2 - 2
server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java

@@ -247,11 +247,11 @@ public class PwmHttpRequestWrapper
         return strValue != null && Boolean.parseBoolean( strValue );
         return strValue != null && Boolean.parseBoolean( strValue );
     }
     }
 
 
-    public <E extends Enum<E>> E readParameterAsEnum( final String name, final Class<E> enumClass, final E defaultValue )
+    public <E extends Enum<E>> Optional<E> readParameterAsEnum( final String name, final Class<E> enumClass )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final String value = readParameterAsString( name, Flag.BypassValidation );
         final String value = readParameterAsString( name, Flag.BypassValidation );
-        return JavaHelper.readEnumFromString( enumClass, defaultValue, value );
+        return JavaHelper.readEnumFromString( enumClass, value );
     }
     }
 
 
     public int readParameterAsInt( final String name, final int defaultValue )
     public int readParameterAsInt( final String name, final int defaultValue )

+ 6 - 0
server/src/main/java/password/pwm/http/PwmRequest.java

@@ -46,6 +46,7 @@ import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.Profile;
 import password.pwm.config.profile.Profile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.SetupOtpProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -683,6 +684,11 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return ( SetupOtpProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupOTPProfile );
         return ( SetupOtpProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupOTPProfile );
     }
     }
 
 
+    public SetupResponsesProfile getSetupResponsesProfile() throws PwmUnrecoverableException
+    {
+        return ( SetupResponsesProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupResponsesProfile );
+    }
+
     public UpdateProfileProfile getUpdateAttributeProfile() throws PwmUnrecoverableException
     public UpdateProfileProfile getUpdateAttributeProfile() throws PwmUnrecoverableException
     {
     {
         return ( UpdateProfileProfile ) getProfile( getPwmDomain(), ProfileDefinition.UpdateAttributes );
         return ( UpdateProfileProfile ) getProfile( getPwmDomain(), ProfileDefinition.UpdateAttributes );

+ 2 - 0
server/src/main/java/password/pwm/http/PwmRequestAttribute.java

@@ -48,6 +48,8 @@ public enum PwmRequestAttribute
     AccountInfo,
     AccountInfo,
 
 
     SetupResponses_ResponseInfo,
     SetupResponses_ResponseInfo,
+    SetupResponses_ChallengeSet,
+    SetupResponses_SetupData,
     SetupResponses_AllowSkip,
     SetupResponses_AllowSkip,
 
 
     SetupOtp_QrCodeValue,
     SetupOtp_QrCodeValue,

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ActivateUserBean.java

@@ -57,9 +57,9 @@ public class ActivateUserBean extends PwmSessionBean
 
 
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/AdminBean.java

@@ -40,9 +40,9 @@ public class AdminBean extends PwmSessionBean
     private UserIdentity lastUserDebug;
     private UserIdentity lastUserDebug;
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ChangePasswordBean.java

@@ -153,9 +153,9 @@ public class ChangePasswordBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ConfigGuideBean.java

@@ -48,9 +48,9 @@ public class ConfigGuideBean extends PwmSessionBean
     private FileValue databaseDriver = null;
     private FileValue databaseDriver = null;
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java

@@ -44,9 +44,9 @@ public class ConfigManagerBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/DeleteAccountBean.java

@@ -42,9 +42,9 @@ public class DeleteAccountBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java

@@ -142,9 +142,9 @@ public class ForgottenPasswordBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/GuestRegistrationBean.java

@@ -82,9 +82,9 @@ public class GuestRegistrationBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/LoginServletBean.java

@@ -43,9 +43,9 @@ public class LoginServletBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/NewUserBean.java

@@ -79,9 +79,9 @@ public class NewUserBean extends PwmSessionBean
     private transient VerificationMethodSystem remoteRecoveryMethod;
     private transient VerificationMethodSystem remoteRecoveryMethod;
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/PwmSessionBean.java

@@ -34,7 +34,7 @@ import java.util.Set;
 @Setter
 @Setter
 public abstract class PwmSessionBean implements Serializable
 public abstract class PwmSessionBean implements Serializable
 {
 {
-    public enum Type
+    public enum BeanType
     {
     {
         PUBLIC,
         PUBLIC,
         AUTHENTICATED,
         AUTHENTICATED,
@@ -50,7 +50,7 @@ public abstract class PwmSessionBean implements Serializable
     private Instant timestamp;
     private Instant timestamp;
     private ErrorInformation lastError;
     private ErrorInformation lastError;
 
 
-    public abstract Type getType( );
+    public abstract BeanType getBeanType( );
 
 
     public abstract Set<SessionBeanMode> supportedModes( );
     public abstract Set<SessionBeanMode> supportedModes( );
 
 

+ 2 - 2
server/src/main/java/password/pwm/http/bean/SetupOtpBean.java

@@ -138,9 +138,9 @@ public class SetupOtpBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 8 - 9
server/src/main/java/password/pwm/http/bean/SetupResponsesBean.java

@@ -21,13 +21,15 @@
 package password.pwm.http.bean;
 package password.pwm.http.bean;
 
 
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.Challenge;
-import com.novell.ldapchai.cr.ChallengeSet;
 import lombok.Data;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.config.option.SessionBeanMode;
+import password.pwm.http.servlet.setupresponses.ResponseMode;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
@@ -37,25 +39,22 @@ import java.util.Set;
 public class SetupResponsesBean extends PwmSessionBean
 public class SetupResponsesBean extends PwmSessionBean
 {
 {
     private boolean hasExistingResponses;
     private boolean hasExistingResponses;
-    private SetupData responseData;
-    private SetupData helpdeskResponseData;
-    private boolean responsesSatisfied;
-    private boolean helpdeskResponsesSatisfied;
+    private final Map<ResponseMode, SetupData> challengeData = new HashMap<>();
+    private final Set<ResponseMode> responsesSatisfied = new HashSet<>();
     private boolean confirmed;
     private boolean confirmed;
     private Locale userLocale;
     private Locale userLocale;
     private boolean initialized;
     private boolean initialized;
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Data
     @Data
     @EqualsAndHashCode( callSuper = false )
     @EqualsAndHashCode( callSuper = false )
     public static class SetupData implements Serializable
     public static class SetupData implements Serializable
     {
     {
-        private ChallengeSet challengeSet;
         private Map<String, Challenge> indexedChallenges = Collections.emptyMap();
         private Map<String, Challenge> indexedChallenges = Collections.emptyMap();
         private boolean simpleMode;
         private boolean simpleMode;
         private int minRandomSetup;
         private int minRandomSetup;
@@ -65,6 +64,6 @@ public class SetupResponsesBean extends PwmSessionBean
     @Override
     @Override
     public Set<SessionBeanMode> supportedModes( )
     public Set<SessionBeanMode> supportedModes( )
     {
     {
-        return Collections.singleton( SessionBeanMode.LOCAL );
+        return Set.of( SessionBeanMode.LOCAL );
     }
     }
 }
 }

+ 2 - 2
server/src/main/java/password/pwm/http/bean/ShortcutsBean.java

@@ -44,9 +44,9 @@ public class ShortcutsBean extends PwmSessionBean
     }
     }
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/http/bean/UpdateProfileBean.java

@@ -61,9 +61,9 @@ public class UpdateProfileBean extends PwmSessionBean
     private boolean tokenSent;
     private boolean tokenSent;
 
 
     @Override
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
     }
 
 
     @Override
     @Override

+ 2 - 1
server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java

@@ -55,6 +55,7 @@ import password.pwm.http.servlet.newuser.NewUserServlet;
 import password.pwm.http.servlet.oauth.OAuthConsumerServlet;
 import password.pwm.http.servlet.oauth.OAuthConsumerServlet;
 import password.pwm.http.servlet.peoplesearch.PrivatePeopleSearchServlet;
 import password.pwm.http.servlet.peoplesearch.PrivatePeopleSearchServlet;
 import password.pwm.http.servlet.peoplesearch.PublicPeopleSearchServlet;
 import password.pwm.http.servlet.peoplesearch.PublicPeopleSearchServlet;
+import password.pwm.http.servlet.setupresponses.SetupResponsesServlet;
 import password.pwm.http.servlet.updateprofile.UpdateProfileServlet;
 import password.pwm.http.servlet.updateprofile.UpdateProfileServlet;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
@@ -77,7 +78,7 @@ public enum PwmServletDefinition
 
 
     AccountInformation( AccountInformationServlet.class, null ),
     AccountInformation( AccountInformationServlet.class, null ),
     PrivateChangePassword( PrivateChangePasswordServlet.class, ChangePasswordBean.class, Flag.RequiresUserPasswordAndBind ),
     PrivateChangePassword( PrivateChangePasswordServlet.class, ChangePasswordBean.class, Flag.RequiresUserPasswordAndBind ),
-    SetupResponses( password.pwm.http.servlet.SetupResponsesServlet.class, SetupResponsesBean.class, Flag.RequiresUserPasswordAndBind ),
+    SetupResponses( SetupResponsesServlet.class, SetupResponsesBean.class, Flag.RequiresUserPasswordAndBind ),
     UpdateProfile( UpdateProfileServlet.class, UpdateProfileBean.class, Flag.RequiresUserPasswordAndBind ),
     UpdateProfile( UpdateProfileServlet.class, UpdateProfileBean.class, Flag.RequiresUserPasswordAndBind ),
     SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class, Flag.RequiresUserPasswordAndBind ),
     SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class, Flag.RequiresUserPasswordAndBind ),
     Helpdesk( password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null ),
     Helpdesk( password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null ),

+ 16 - 12
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java

@@ -194,7 +194,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
     public ProcessStatus handleResetRequest( final PwmRequest pwmRequest )
     public ProcessStatus handleResetRequest( final PwmRequest pwmRequest )
             throws ServletException, PwmUnrecoverableException, IOException
             throws ServletException, PwmUnrecoverableException, IOException
     {
     {
-        final ResetType resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetType.class, ResetType.exitActivation );
+        final ResetType resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetType.class ).orElse( ResetType.exitActivation );
 
 
         switch ( resetType )
         switch ( resetType )
         {
         {
@@ -339,10 +339,12 @@ public class ActivateUserServlet extends ControlledPwmServlet
                     TokenService.TokenEntryType.unauthenticated
                     TokenService.TokenEntryType.unauthenticated
             );
             );
 
 
-            activateUserBean.setUserIdentity( tokenPayload.getUserIdentity() );
+            if ( activateUserBean.getUserIdentity() == null )
+            {
+                ActivateUserUtils.initUserActivationBean( pwmRequest, tokenPayload.getUserIdentity() );
+            }
+
             activateUserBean.setTokenPassed( true );
             activateUserBean.setTokenPassed( true );
-            activateUserBean.setFormValidated( true );
-            activateUserBean.setTokenDestination( tokenPayload.getDestination() );
             activateUserBean.setTokenSent( true );
             activateUserBean.setTokenSent( true );
 
 
             if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) )
             if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) )
@@ -415,7 +417,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
         final ActivateUserProfile activateUserProfile = activateUserProfile( pwmRequest );
         final ActivateUserProfile activateUserProfile = activateUserProfile( pwmRequest );
 
 
         final MessageSendMethod tokenSendMethod = activateUserProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
         final MessageSendMethod tokenSendMethod = activateUserProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
-        if ( tokenSendMethod != MessageSendMethod.NONE && tokenSendMethod != null )
+        if ( !activateUserBean.isTokenPassed() && tokenSendMethod != MessageSendMethod.NONE && tokenSendMethod != null )
         {
         {
             final List<TokenDestinationItem> tokenDestinationItems = TokenUtil.figureAvailableTokenDestinations(
             final List<TokenDestinationItem> tokenDestinationItems = TokenUtil.figureAvailableTokenDestinations(
                     pwmDomain,
                     pwmDomain,
@@ -461,14 +463,16 @@ public class ActivateUserServlet extends ControlledPwmServlet
             }
             }
         }
         }
 
 
-        final String agreementText = activateUserProfile.readSettingAsLocalizedString(
-                PwmSetting.ACTIVATE_AGREEMENT_MESSAGE,
-                pwmSession.getSessionStateBean().getLocale()
-        );
-        if ( StringUtil.notEmpty( agreementText ) && !activateUserBean.isAgreementPassed() )
         {
         {
-            ActivateUserUtils.forwardToAgreementPage( pwmRequest );
-            return;
+            final String agreementText = activateUserProfile.readSettingAsLocalizedString(
+                    PwmSetting.ACTIVATE_AGREEMENT_MESSAGE,
+                    pwmSession.getSessionStateBean().getLocale() );
+
+            if ( StringUtil.notEmpty( agreementText ) && !activateUserBean.isAgreementPassed() )
+            {
+                ActivateUserUtils.forwardToAgreementPage( pwmRequest );
+                return;
+            }
         }
         }
 
 
         try
         try

+ 4 - 3
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -601,8 +601,8 @@ public class ConfigEditorServlet extends ControlledPwmServlet
                     final SearchResultItem item = SearchResultItem.fromKey( recordID, storedConfiguration, locale );
                     final SearchResultItem item = SearchResultItem.fromKey( recordID, storedConfiguration, locale );
                     final String returnCategory = item.getNavigation();
                     final String returnCategory = item.getNavigation();
 
 
-                    returnData.computeIfAbsent( returnCategory, k -> new TreeMap<>() );
-                    returnData.get( returnCategory ).put( recordID.getRecordID(), item );
+                    returnData.computeIfAbsent( returnCategory, k -> new TreeMap<>() )
+                            .put( recordID.getRecordID(), item );
                 } );
                 } );
 
 
 
 
@@ -813,9 +813,10 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final NavTreeSettings navTreeSettings = NavTreeSettings.readFromRequest( pwmRequest );
         final NavTreeSettings navTreeSettings = NavTreeSettings.readFromRequest( pwmRequest );
 
 
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
+        final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle();
 
 
         final List<NavTreeItem> navigationData = NavTreeDataMaker.makeNavTreeItems(
         final List<NavTreeItem> navigationData = NavTreeDataMaker.makeNavTreeItems(
-                pwmRequest.getPwmDomain(),
+                domainID,
                 storedConfiguration,
                 storedConfiguration,
                 navTreeSettings );
                 navTreeSettings );
 
 

+ 76 - 70
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java

@@ -21,8 +21,6 @@
 package password.pwm.http.servlet.configeditor.data;
 package password.pwm.http.servlet.configeditor.data;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
-import password.pwm.PwmDomain;
-import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
@@ -51,6 +49,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.ResourceBundle;
 import java.util.ResourceBundle;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * Utility class for generating {@link NavTreeItem}s suitable for display in the
  * Utility class for generating {@link NavTreeItem}s suitable for display in the
@@ -65,7 +64,7 @@ public class NavTreeDataMaker
     private static final String DISPLAY_TEXT_NAME = "Display Text";
     private static final String DISPLAY_TEXT_NAME = "Display Text";
 
 
     public static List<NavTreeItem> makeNavTreeItems(
     public static List<NavTreeItem> makeNavTreeItems(
-            final PwmDomain pwmDomain,
+            final DomainID domainID,
             final StoredConfiguration storedConfiguration,
             final StoredConfiguration storedConfiguration,
             final NavTreeSettings navTreeSettings
             final NavTreeSettings navTreeSettings
     )
     )
@@ -77,10 +76,10 @@ public class NavTreeDataMaker
         navigationData.add( makeRootNode() );
         navigationData.add( makeRootNode() );
 
 
         // add setting nodes
         // add setting nodes
-        navigationData.addAll( makeSettingNavItems( pwmDomain, storedConfiguration, navTreeSettings ) );
+        navigationData.addAll( makeCategoryNavItems( domainID, storedConfiguration, navTreeSettings ) );
 
 
         // add display text nodes
         // add display text nodes
-        navigationData.addAll( makeDisplayTextNavItems( pwmDomain, storedConfiguration, navTreeSettings ) );
+        navigationData.addAll( makeDisplayTextNavItems( domainID, storedConfiguration, navTreeSettings ) );
 
 
         NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.NOTES.toString(), navigationData );
         NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.NOTES.toString(), navigationData );
         NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.TEMPLATES.toString(), navigationData );
         NavTreeDataMaker.moveNavItemToTopOfList( PwmSettingCategory.TEMPLATES.toString(), navigationData );
@@ -100,13 +99,13 @@ public class NavTreeDataMaker
     }
     }
 
 
     private static List<NavTreeItem> makeDisplayTextNavItems(
     private static List<NavTreeItem> makeDisplayTextNavItems(
-            final PwmDomain pwmDomain,
+            final DomainID domainId,
             final StoredConfiguration storedConfiguration,
             final StoredConfiguration storedConfiguration,
             final NavTreeSettings navTreeSettings
             final NavTreeSettings navTreeSettings
     )
     )
     {
     {
         final DomainID domainID = navTreeSettings.getDomainManageMode() == DomainManageMode.domain
         final DomainID domainID = navTreeSettings.getDomainManageMode() == DomainManageMode.domain
-                ? pwmDomain.getDomainID()
+                ? domainId
                 : DomainID.systemId();
                 : DomainID.systemId();
 
 
         return makeDisplayTextNavItemsForDomain( domainID, storedConfiguration, navTreeSettings );
         return makeDisplayTextNavItemsForDomain( domainID, storedConfiguration, navTreeSettings );
@@ -204,80 +203,87 @@ public class NavTreeDataMaker
     /**
     /**
      * Produces a collection of {@code NavTreeItem}.
      * Produces a collection of {@code NavTreeItem}.
      */
      */
-    private static List<NavTreeItem> makeSettingNavItems(
-            final PwmDomain pwmDomain,
+    private static List<NavTreeItem> makeCategoryNavItems(
+            final DomainID domainId,
+            final StoredConfiguration storedConfiguration,
+            final NavTreeSettings navTreeSettings
+    )
+    {
+        return PwmSettingCategory.sortedValues().stream()
+                .filter( loopCategory -> categoryMatcher( domainId, loopCategory, null, storedConfiguration, navTreeSettings ) )
+                .flatMap( loopCategory -> navTreeItemsForCategory( loopCategory, domainId, storedConfiguration, navTreeSettings ).stream() )
+                        .collect( Collectors.toUnmodifiableList() );
+
+    }
+
+    private static List<NavTreeItem> navTreeItemsForCategory(
+            final PwmSettingCategory loopCategory,
+            final DomainID domainId,
             final StoredConfiguration storedConfiguration,
             final StoredConfiguration storedConfiguration,
             final NavTreeSettings navTreeSettings
             final NavTreeSettings navTreeSettings
     )
     )
     {
     {
         final Locale locale = navTreeSettings.getLocale();
         final Locale locale = navTreeSettings.getLocale();
-        final List<NavTreeItem> navigationData = new ArrayList<>();
 
 
-        for ( final PwmSettingCategory loopCategory : PwmSettingCategory.sortedValues() )
+        if ( !loopCategory.hasProfiles() )
         {
         {
-            if ( !loopCategory.hasProfiles() )
-            {
-                // regular category, so output a standard nav tree item
-                if ( categoryMatcher( pwmDomain, loopCategory, null, storedConfiguration, navTreeSettings ) )
-                {
-                    navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
-                }
-            }
-            else
-            {
-                final List<String> profiles = StoredConfigurationUtil.profilesForCategory( pwmDomain.getDomainID(), loopCategory, storedConfiguration );
+            // regular category, so output a standard nav tree item
+            return List.of( navTreeItemForCategory( loopCategory, locale, null ) );
+        }
 
 
-                if ( loopCategory.isTopLevelProfile() )
-                {
-                    // edit profile option
-                    navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
+        final List<String> profiles = StoredConfigurationUtil.profilesForCategory( domainId, loopCategory, storedConfiguration );
+        if ( loopCategory.isTopLevelProfile() )
+        {
+            final List<NavTreeItem> navigationData = new ArrayList<>();
 
 
-                    {
-                        final String editItemName = LocaleHelper.getLocalizedMessage( locale, Config.Label_ProfileListEditMenuItem, null );
-                        final PwmSetting profileSetting = loopCategory.getProfileSetting().orElseThrow( IllegalStateException::new );
-
-                        final NavTreeItem profileEditorInfo = NavTreeItem.builder()
-                                .id( loopCategory.getKey() + "-EDITOR" )
-                                .name( editItemName )
-                                .type(  NavTreeItem.NavItemType.profileDefinition )
-                                .profileSetting( profileSetting.getKey() )
-                                .parent( loopCategory.getKey() )
-                                .build();
-                        navigationData.add( profileEditorInfo );
-                    }
+            // edit profile option
+            navigationData.add( navTreeItemForCategory( loopCategory, locale, null ) );
 
 
-                    for ( final String profileId : profiles )
-                    {
-                        final NavTreeItem.NavItemType type = !loopCategory.hasChildren()
-                                ? NavTreeItem.NavItemType.category
-                                : NavTreeItem.NavItemType.navigation;
-
-                        final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId ).toBuilder()
-                                .name(  profileId.isEmpty() ? "Default" : profileId )
-                                .id( "profile-" + loopCategory.getKey() + "-" + profileId )
-                                .parent( loopCategory.getKey() )
-                                .type( type )
-                                .build();
+            {
+                final String editItemName = LocaleHelper.getLocalizedMessage( locale, Config.Label_ProfileListEditMenuItem, null );
+                final PwmSetting profileSetting = loopCategory.getProfileSetting().orElseThrow( IllegalStateException::new );
+
+                final NavTreeItem profileEditorInfo = NavTreeItem.builder()
+                        .id( loopCategory.getKey() + "-EDITOR" )
+                        .name( editItemName )
+                        .type( NavTreeItem.NavItemType.profileDefinition )
+                        .profileSetting( profileSetting.getKey() )
+                        .parent( loopCategory.getKey() )
+                        .build();
+                navigationData.add( profileEditorInfo );
+            }
 
 
-                        navigationData.add( profileInfo );
-                    }
-                }
-                else
-                {
-                    for ( final String profileId : profiles )
-                    {
-                        if ( categoryMatcher( pwmDomain, loopCategory, profileId, storedConfiguration, navTreeSettings ) )
-                        {
-                            navigationData.add( navTreeItemForCategory( loopCategory, locale, profileId ) );
-                        }
-                    }
-                }
+            for ( final String profileId : profiles )
+            {
+                final NavTreeItem.NavItemType type = !loopCategory.hasChildren()
+                        ? NavTreeItem.NavItemType.category
+                        : NavTreeItem.NavItemType.navigation;
+
+                final NavTreeItem profileInfo = navTreeItemForCategory( loopCategory, locale, profileId ).toBuilder()
+                        .name( profileId.isEmpty() ? "Default" : profileId )
+                        .id( "profile-" + loopCategory.getKey() + "-" + profileId )
+                        .parent( loopCategory.getKey() )
+                        .type( type )
+                        .build();
+
+                navigationData.add( profileInfo );
             }
             }
+
+            return Collections.unmodifiableList( navigationData );
         }
         }
 
 
-        return navigationData;
+        final List<NavTreeItem> navigationData = new ArrayList<>();
+        for ( final String profileId : profiles )
+        {
+            if ( categoryMatcher( domainId, loopCategory, profileId, storedConfiguration, navTreeSettings ) )
+            {
+                navigationData.add( navTreeItemForCategory( loopCategory, locale, profileId ) );
+            }
+        }
+        return Collections.unmodifiableList( navigationData );
     }
     }
 
 
+
     private static NavTreeItem navTreeItemForCategory(
     private static NavTreeItem navTreeItemForCategory(
             final PwmSettingCategory category,
             final PwmSettingCategory category,
             final Locale locale,
             final Locale locale,
@@ -304,7 +310,7 @@ public class NavTreeDataMaker
     }
     }
 
 
     private static boolean categoryMatcher(
     private static boolean categoryMatcher(
-            final PwmDomain pwmDomain,
+            final DomainID domainID,
             final PwmSettingCategory category,
             final PwmSettingCategory category,
             final String profile,
             final String profile,
             final StoredConfiguration storedConfiguration,
             final StoredConfiguration storedConfiguration,
@@ -313,7 +319,7 @@ public class NavTreeDataMaker
     {
     {
         if ( category == PwmSettingCategory.HTTPS_SERVER )
         if ( category == PwmSettingCategory.HTTPS_SERVER )
         {
         {
-            if ( !pwmDomain.getPwmApplication().getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps ) )
+            if ( !navTreeSettings.isMangeHttps() )
             {
             {
                 return false;
                 return false;
             }
             }
@@ -321,7 +327,7 @@ public class NavTreeDataMaker
 
 
         for ( final PwmSettingCategory childCategory : category.getChildren() )
         for ( final PwmSettingCategory childCategory : category.getChildren() )
         {
         {
-            if ( categoryMatcher( pwmDomain, childCategory, profile, storedConfiguration, navTreeSettings ) )
+            if ( categoryMatcher( domainID, childCategory, profile, storedConfiguration, navTreeSettings ) )
             {
             {
                 return true;
                 return true;
             }
             }
@@ -329,7 +335,7 @@ public class NavTreeDataMaker
 
 
         for ( final PwmSetting setting : category.getSettings() )
         for ( final PwmSetting setting : category.getSettings() )
         {
         {
-            if ( settingMatcher( pwmDomain.getDomainID(), storedConfiguration, setting, profile, navTreeSettings ) )
+            if ( settingMatcher( domainID, storedConfiguration, setting, profile, navTreeSettings ) )
             {
             {
                 return true;
                 return true;
             }
             }
@@ -354,7 +360,7 @@ public class NavTreeDataMaker
         }
         }
 
 
         final boolean valueIsDefault = StoredConfigurationUtil.isDefaultValue( storedConfiguration, storedConfigKey );
         final boolean valueIsDefault = StoredConfigurationUtil.isDefaultValue( storedConfiguration, storedConfigKey );
-        if ( setting.isHidden() && !valueIsDefault )
+        if ( setting.isHidden() && !valueIsDefault && setting.getSyntax() != PwmSettingSyntax.PROFILE )
         {
         {
             return false;
             return false;
         }
         }

+ 5 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java

@@ -23,6 +23,7 @@ package password.pwm.http.servlet.configeditor.data;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.PwmEnvironment;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
@@ -50,6 +51,8 @@ public class NavTreeSettings implements Serializable
 
 
     private final DomainManageMode domainManageMode;
     private final DomainManageMode domainManageMode;
 
 
+    private final boolean mangeHttps;
+
     public static NavTreeSettings forBasic()
     public static NavTreeSettings forBasic()
     {
     {
         return NavTreeSettings.builder()
         return NavTreeSettings.builder()
@@ -64,10 +67,12 @@ public class NavTreeSettings implements Serializable
         final int level = ( int ) ( ( double ) inputParameters.get( "level" ) );
         final int level = ( int ) ( ( double ) inputParameters.get( "level" ) );
         final String filterText = ( String ) inputParameters.get( "text" );
         final String filterText = ( String ) inputParameters.get( "text" );
         final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
         final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
+        final boolean manageHttps = pwmRequest.getPwmApplication().getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps );
 
 
         return NavTreeSettings.builder()
         return NavTreeSettings.builder()
                 .modifiedSettingsOnly( modifiedSettingsOnly )
                 .modifiedSettingsOnly( modifiedSettingsOnly )
                 .domainManageMode( domainStateReader.getMode() )
                 .domainManageMode( domainStateReader.getMode() )
+                .mangeHttps( manageHttps )
                 .level( level )
                 .level( level )
                 .filterText( filterText )
                 .filterText( filterText )
                 .locale( pwmRequest.getLocale() )
                 .locale( pwmRequest.getLocale() )

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java

@@ -291,7 +291,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
     private ProcessStatus processReset( final PwmRequest pwmRequest )
     private ProcessStatus processReset( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
-        final ResetAction resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetAction.class, ResetAction.exitForgottenPassword );
+        final ResetAction resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetAction.class ).orElse( ResetAction.exitForgottenPassword );
 
 
         switch ( resetType )
         switch ( resetType )
         {
         {

+ 18 - 33
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -133,7 +133,7 @@ public class ForgottenPasswordUtil
         return Collections.unmodifiableSet( result );
         return Collections.unmodifiableSet( result );
     }
     }
 
 
-    static Optional<UserInfo> readUserInfo(
+    public static Optional<UserInfo> readUserInfo(
             final PwmRequestContext pwmRequestContext,
             final PwmRequestContext pwmRequestContext,
             final ForgottenPasswordBean forgottenPasswordBean
             final ForgottenPasswordBean forgottenPasswordBean
     )
     )
@@ -167,23 +167,12 @@ public class ForgottenPasswordUtil
 
 
         final PwmDomain pwmDomain = pwmRequestContext.getPwmDomain();
         final PwmDomain pwmDomain = pwmRequestContext.getPwmDomain();
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
-        final Optional<ResponseSet> responseSet;
 
 
-        try
-        {
-            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequestContext.getSessionLabel(), userIdentity );
-            responseSet = pwmDomain.getCrService().readUserResponseSet(
-                    pwmRequestContext.getSessionLabel(),
-                    userIdentity,
-                    theUser
-            );
-        }
-        catch ( final ChaiUnavailableException e )
-        {
-            throw PwmUnrecoverableException.fromChaiException( e );
-        }
-
-        return responseSet;
+        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequestContext.getSessionLabel(), userIdentity );
+        return pwmDomain.getCrService().readUserResponseSet(
+                pwmRequestContext.getSessionLabel(),
+                userIdentity,
+                theUser );
     }
     }
 
 
     static void sendUnlockNoticeEmail(
     static void sendUnlockNoticeEmail(
@@ -322,7 +311,8 @@ public class ForgottenPasswordUtil
                     throw new PwmUnrecoverableException( errorInformation );
                     throw new PwmUnrecoverableException( errorInformation );
                 }
                 }
 
 
-                final ChallengeSet challengeSet = userInfo.getChallengeProfile().getChallengeSet();
+                final ChallengeSet challengeSet = userInfo.getChallengeProfile().getChallengeSet()
+                        .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES ) );
 
 
                 try
                 try
                 {
                 {
@@ -525,7 +515,7 @@ public class ForgottenPasswordUtil
                     PwmError.ERROR_INTERNAL,
                     PwmError.ERROR_INTERNAL,
                     "unexpected ldap error while processing recovery action " + recoveryAction + ", error: " + e.getMessage()
                     "unexpected ldap error while processing recovery action " + recoveryAction + ", error: " + e.getMessage()
             );
             );
-            LOGGER.warn( pwmRequest, () -> errorInformation.toDebugStr() );
+            LOGGER.warn( pwmRequest, errorInformation::toDebugStr );
             pwmRequest.respondWithError( errorInformation );
             pwmRequest.respondWithError( errorInformation );
         }
         }
         finally
         finally
@@ -549,7 +539,8 @@ public class ForgottenPasswordUtil
         final List<Challenge> challengeList;
         final List<Challenge> challengeList;
         {
         {
             final String firstProfile = pwmRequestContext.getDomainConfig().getChallengeProfileIDs().iterator().next();
             final String firstProfile = pwmRequestContext.getDomainConfig().getChallengeProfileIDs().iterator().next();
-            final ChallengeSet challengeSet = pwmRequestContext.getDomainConfig().getChallengeProfile( firstProfile, PwmConstants.DEFAULT_LOCALE ).getChallengeSet();
+            final ChallengeSet challengeSet = pwmRequestContext.getDomainConfig().getChallengeProfile( firstProfile, PwmConstants.DEFAULT_LOCALE ).getChallengeSet()
+                    .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES.toInfo() ) );
             challengeList = new ArrayList<>( challengeSet.getRequiredChallenges() );
             challengeList = new ArrayList<>( challengeSet.getRequiredChallenges() );
             for ( int i = 0; i < challengeSet.getMinRandomRequired(); i++ )
             for ( int i = 0; i < challengeSet.getMinRandomRequired(); i++ )
             {
             {
@@ -596,7 +587,6 @@ public class ForgottenPasswordUtil
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity
             final UserIdentity userIdentity
     )
     )
-            throws PwmUnrecoverableException
     {
     {
         ForgottenPasswordProfile forgottenPasswordProfile = null;
         ForgottenPasswordProfile forgottenPasswordProfile = null;
         try
         try
@@ -634,9 +624,9 @@ public class ForgottenPasswordUtil
     {
     {
         final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser(
         final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser(
                 pwmDomain,
                 pwmDomain,
-            sessionLabel,
-            userIdentity,
-            ProfileDefinition.ForgottenPassword
+                sessionLabel,
+                userIdentity,
+                ProfileDefinition.ForgottenPassword
         );
         );
 
 
         if ( profileID.isPresent() )
         if ( profileID.isPresent() )
@@ -712,10 +702,6 @@ public class ForgottenPasswordUtil
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
                 throw new PwmUnrecoverableException( errorInformation );
                 throw new PwmUnrecoverableException( errorInformation );
             }
             }
-            catch ( final ChaiUnavailableException e )
-            {
-                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
-            }
         }
         }
         else
         else
         {
         {
@@ -746,7 +732,7 @@ public class ForgottenPasswordUtil
             }
             }
         }
         }
 
 
-        final List<FormConfiguration> attributeForm = figureAttributeForm( forgottenPasswordProfile, forgottenPasswordBean, pwmRequestContext, userIdentity );
+        final List<FormConfiguration> attributeForm = figureAttributeForm( forgottenPasswordProfile, forgottenPasswordBean, pwmRequestContext );
 
 
         forgottenPasswordBean.setUserLocale( locale );
         forgottenPasswordBean.setUserLocale( locale );
         forgottenPasswordBean.setPresentableChallengeSet( challengeSet == null ? null : challengeSet.asChallengeSetBean() );
         forgottenPasswordBean.setPresentableChallengeSet( challengeSet == null ? null : challengeSet.asChallengeSetBean() );
@@ -764,8 +750,7 @@ public class ForgottenPasswordUtil
     static List<FormConfiguration> figureAttributeForm(
     static List<FormConfiguration> figureAttributeForm(
             final ForgottenPasswordProfile forgottenPasswordProfile,
             final ForgottenPasswordProfile forgottenPasswordProfile,
             final ForgottenPasswordBean forgottenPasswordBean,
             final ForgottenPasswordBean forgottenPasswordBean,
-            final PwmRequestContext pwmRequestContext,
-            final UserIdentity userIdentity
+            final PwmRequestContext pwmRequestContext
     )
     )
             throws PwmOperationalException, PwmUnrecoverableException
             throws PwmOperationalException, PwmUnrecoverableException
     {
     {
@@ -839,13 +824,13 @@ public class ForgottenPasswordUtil
             final IdentityVerificationMethod thisMethod
             final IdentityVerificationMethod thisMethod
     )
     )
     {
     {
-        if ( forgottenPasswordBean.getRecoveryFlags().getRequiredAuthMethods().contains( thisMethod )  )
+        if ( forgottenPasswordBean.getRecoveryFlags().getRequiredAuthMethods().contains( thisMethod ) )
         {
         {
             return false;
             return false;
         }
         }
 
 
         {
         {
-            // check if has previously satisfied any other optional methods.
+            // check if previously satisfied any other optional methods.
             final Set<IdentityVerificationMethod> optionalAuthMethods = forgottenPasswordBean.getRecoveryFlags().getOptionalAuthMethods();
             final Set<IdentityVerificationMethod> optionalAuthMethods = forgottenPasswordBean.getRecoveryFlags().getOptionalAuthMethods();
             final Set<IdentityVerificationMethod> satisfiedMethods = forgottenPasswordBean.getProgress().getSatisfiedMethods();
             final Set<IdentityVerificationMethod> satisfiedMethods = forgottenPasswordBean.getProgress().getSatisfiedMethods();
             final boolean disJoint = Collections.disjoint( optionalAuthMethods, satisfiedMethods );
             final boolean disJoint = Collections.disjoint( optionalAuthMethods, satisfiedMethods );

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java

@@ -188,7 +188,7 @@ public class HelpdeskDetailInfoBean implements Serializable
 
 
         {
         {
             final ResponseInfoBean responseInfoBean = userInfo.getResponseInfoBean();
             final ResponseInfoBean responseInfoBean = userInfo.getResponseInfoBean();
-            if ( responseInfoBean != null && responseInfoBean.getHelpdeskCrMap() != null )
+            if (  responseInfoBean.getHelpdeskCrMap() != null )
             {
             {
                 final List<DisplayElement> responseDisplay = new ArrayList<>();
                 final List<DisplayElement> responseDisplay = new ArrayList<>();
                 int counter = 0;
                 int counter = 0;

+ 27 - 0
server/src/main/java/password/pwm/http/servlet/setupresponses/ResponseMode.java

@@ -0,0 +1,27 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.http.servlet.setupresponses;
+
+public enum ResponseMode
+{
+    user,
+    helpdesk,
+}

+ 122 - 359
server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java → server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java

@@ -18,26 +18,20 @@
  * limitations under the License.
  * limitations under the License.
  */
  */
 
 
-package password.pwm.http.servlet;
+package password.pwm.http.servlet.setupresponses;
 
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.cr.ChaiChallenge;
-import com.novell.ldapchai.cr.ChaiCrFactory;
-import com.novell.ldapchai.cr.ChaiResponseSet;
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ChallengeSet;
-import com.novell.ldapchai.exception.ChaiError;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiValidationException;
 import com.novell.ldapchai.exception.ChaiValidationException;
-import com.novell.ldapchai.provider.ChaiProvider;
-import lombok.Value;
-import password.pwm.Permission;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmDomain;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.ChallengeProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
@@ -50,6 +44,8 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.SetupResponsesBean;
 import password.pwm.http.bean.SetupResponsesBean;
+import password.pwm.http.servlet.ControlledPwmServlet;
+import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationType;
@@ -67,11 +63,9 @@ import password.pwm.ws.server.RestResultBean;
 import javax.servlet.ServletException;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
 import java.io.IOException;
-import java.io.Serializable;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map;
 
 
 /**
 /**
@@ -89,10 +83,11 @@ import java.util.Map;
 )
 )
 public class SetupResponsesServlet extends ControlledPwmServlet
 public class SetupResponsesServlet extends ControlledPwmServlet
 {
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( SetupResponsesServlet.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( SetupResponsesServlet.class );
 
 
-    public enum SetupResponsesAction implements AbstractPwmServlet.ProcessAction
+    public static final String PARAM_RESPONSE_MODE = "responseMode";
+
+    public enum SetupResponsesAction implements ProcessAction
     {
     {
         validateResponses( HttpMethod.POST ),
         validateResponses( HttpMethod.POST ),
         setResponses( HttpMethod.POST ),
         setResponses( HttpMethod.POST ),
@@ -122,19 +117,39 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         return SetupResponsesAction.class;
         return SetupResponsesAction.class;
     }
     }
 
 
-    private SetupResponsesBean getSetupResponseBean( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    private SetupResponsesBean getSetupResponseBean( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
     {
-        final SetupResponsesBean setupResponsesBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class );
-        if ( !setupResponsesBean.isInitialized() )
+        return pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class );
+    }
+
+
+    static SetupResponsesProfile getSetupProfile( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    {
+        return pwmRequest.getSetupResponsesProfile( );
+    }
+
+    static ChallengeProfile getChallengeProfile( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        return pwmRequest.getPwmSession().getUserInfo().getChallengeProfile();
+    }
+
+    static ChallengeSet getChallengeSet( final PwmRequest pwmRequest, final ResponseMode responseMode )
+            throws PwmUnrecoverableException
+    {
+        final ChallengeProfile challengeProfile = getChallengeProfile( pwmRequest );
+        if ( responseMode == ResponseMode.helpdesk )
         {
         {
-            initializeBean( pwmRequest, setupResponsesBean );
+            return challengeProfile.getHelpdeskChallengeSet().orElseThrow();
         }
         }
-        return setupResponsesBean;
-
+        return challengeProfile.getChallengeSet().orElseThrow();
     }
     }
 
 
+
     @Override
     @Override
-    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
+    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException, IOException, ServletException
     {
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -150,17 +165,11 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             throw new PwmUnrecoverableException( PwmError.ERROR_PASSWORD_REQUIRED );
             throw new PwmUnrecoverableException( PwmError.ERROR_PASSWORD_REQUIRED );
         }
         }
 
 
-        if ( !pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.CHALLENGE_ENABLE ) )
+        if ( !pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.SETUP_RESPONSE_ENABLE ) )
         {
         {
             throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE );
             throw new PwmUnrecoverableException( PwmError.ERROR_SERVICE_NOT_AVAILABLE );
         }
         }
 
 
-        // check to see if the user is permitted to setup responses
-        if ( !pwmSession.getSessionManager().checkPermission( pwmDomain, Permission.SETUP_RESPONSE ) )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_UNAUTHORIZED );
-        }
-
         // check if the locale has changed since first seen.
         // check if the locale has changed since first seen.
         if ( pwmSession.getSessionStateBean().getLocale() != pwmDomain.getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class ).getUserLocale() )
         if ( pwmSession.getSessionStateBean().getLocale() != pwmDomain.getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class ).getUserLocale() )
         {
         {
@@ -168,11 +177,17 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             pwmDomain.getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class ).setUserLocale( pwmSession.getSessionStateBean().getLocale() );
             pwmDomain.getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class ).setUserLocale( pwmSession.getSessionStateBean().getLocale() );
         }
         }
 
 
+        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
+        if ( !setupResponsesBean.isInitialized() )
+        {
+            initializeBean( pwmRequest, setupResponsesBean );
+        }
+
         // check to see if the user has any challenges assigned
         // check to see if the user has any challenges assigned
         final UserInfo uiBean = pwmSession.getUserInfo();
         final UserInfo uiBean = pwmSession.getUserInfo();
-        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
 
 
-        if ( setupResponsesBean.getResponseData().getChallengeSet() == null || setupResponsesBean.getResponseData().getChallengeSet().getChallenges().isEmpty() )
+        if ( !SetupResponsesUtil.hasChallenges( pwmRequest, ResponseMode.user )
+                && !SetupResponsesUtil.hasChallenges( pwmRequest, ResponseMode.helpdesk ) )
         {
         {
             final String errorMsg = "no challenge sets configured for user " + uiBean.getUserIdentity();
             final String errorMsg = "no challenge sets configured for user " + uiBean.getUserIdentity();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
@@ -184,7 +199,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     }
     }
 
 
     @ActionHandler( action = "confirmResponses" )
     @ActionHandler( action = "confirmResponses" )
-    private ProcessStatus processConfirmResponses( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    private ProcessStatus processConfirmResponses( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
     {
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         setupResponsesBean.setConfirmed( true );
         setupResponsesBean.setConfirmed( true );
@@ -192,7 +208,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     }
     }
 
 
     @ActionHandler( action = "changeResponses" )
     @ActionHandler( action = "changeResponses" )
-    private ProcessStatus processChangeResponses( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    private ProcessStatus processChangeResponses( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
     {
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -246,7 +263,7 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     {
     {
         LOGGER.trace( pwmRequest, () -> "request for skip received" );
         LOGGER.trace( pwmRequest, () -> "request for skip received" );
 
 
-        final boolean allowSkip = checkIfAllowSkipCr( pwmRequest );
+        final boolean allowSkip = SetupResponsesUtil.checkIfAllowSkipCr( pwmRequest );
 
 
         if ( allowSkip )
         if ( allowSkip )
         {
         {
@@ -262,32 +279,31 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     private ProcessStatus restValidateResponses(
     private ProcessStatus restValidateResponses(
             final PwmRequest pwmRequest
             final PwmRequest pwmRequest
     )
     )
-            throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
+            throws IOException, PwmUnrecoverableException
     {
     {
-        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final Instant startTime = Instant.now();
         final Instant startTime = Instant.now();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final String responseModeParam = pwmRequest.readParameterAsString( "responseMode" );
-        final SetupResponsesBean.SetupData setupData = "helpdesk".equalsIgnoreCase( responseModeParam )
-                ? setupResponsesBean.getHelpdeskResponseData()
-                : setupResponsesBean.getResponseData();
 
 
-        boolean success = true;
-        String userMessage = Message.getLocalizedMessage( pwmSession.getSessionStateBean().getLocale(), Message.Success_ResponsesMeetRules, pwmDomain.getConfig() );
+        final ResponseMode responseMode = pwmRequest.readParameterAsEnum( PARAM_RESPONSE_MODE, ResponseMode.class ).orElseThrow();
+        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
+
+        final SetupResponsesBean.SetupData setupData = setupResponsesBean.getChallengeData().get( responseMode );
+        final ChallengeSet challengeSet = getChallengeSet( pwmRequest, responseMode );
+
+        boolean success = false;
+        String userMessage = Message.getLocalizedMessage( pwmRequest.getLocale(), Message.Success_ResponsesMeetRules, pwmRequest.getDomainConfig() );
 
 
         try
         try
         {
         {
             // read in the responses from the request
             // read in the responses from the request
             final Map<Challenge, String> responseMap = readResponsesFromJsonRequest( pwmRequest, setupData );
             final Map<Challenge, String> responseMap = readResponsesFromJsonRequest( pwmRequest, setupData );
             final int minRandomRequiredSetup = setupData.getMinRandomSetup();
             final int minRandomRequiredSetup = setupData.getMinRandomSetup();
-            pwmDomain.getCrService().validateResponses( setupData.getChallengeSet(), responseMap, minRandomRequiredSetup );
-            generateResponseInfoBean( pwmRequest, setupData.getChallengeSet(), responseMap, Collections.emptyMap() );
+            pwmRequest.getPwmDomain().getCrService().validateResponses( challengeSet, responseMap, minRandomRequiredSetup );
+            SetupResponsesUtil.generateResponseInfoBean( pwmRequest, challengeSet, responseMap, Collections.emptyMap() );
+            success = true;
         }
         }
         catch ( final PwmDataValidationException e )
         catch ( final PwmDataValidationException e )
         {
         {
-            success = false;
-            userMessage = e.getErrorInformation().toUserStr( pwmSession, pwmDomain.getConfig() );
+            userMessage = e.getErrorInformation().toUserStr( pwmRequest.getPwmSession(), pwmRequest.getAppConfig() );
         }
         }
 
 
         final ValidationResponseBean validationResponseBean = new ValidationResponseBean( userMessage, success );
         final ValidationResponseBean validationResponseBean = new ValidationResponseBean( userMessage, success );
@@ -301,17 +317,17 @@ public class SetupResponsesServlet extends ControlledPwmServlet
 
 
     @ActionHandler( action = "setHelpdeskResponses" )
     @ActionHandler( action = "setHelpdeskResponses" )
     private ProcessStatus processSetHelpdeskResponses( final PwmRequest pwmRequest )
     private ProcessStatus processSetHelpdeskResponses( final PwmRequest pwmRequest )
-            throws ChaiUnavailableException, PwmUnrecoverableException, ServletException, IOException
+            throws PwmUnrecoverableException
     {
     {
-        setupResponses( pwmRequest, true );
+        setupResponses( pwmRequest, ResponseMode.helpdesk );
         return ProcessStatus.Continue;
         return ProcessStatus.Continue;
     }
     }
 
 
     @ActionHandler( action = "setResponses" )
     @ActionHandler( action = "setResponses" )
     private ProcessStatus processSetResponses( final PwmRequest pwmRequest )
     private ProcessStatus processSetResponses( final PwmRequest pwmRequest )
-            throws ChaiUnavailableException, PwmUnrecoverableException, ServletException, IOException
+            throws PwmUnrecoverableException
     {
     {
-        setupResponses( pwmRequest, false );
+        setupResponses( pwmRequest, ResponseMode.user );
         return ProcessStatus.Continue;
         return ProcessStatus.Continue;
     }
     }
 
 
@@ -321,45 +337,34 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     {
     {
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
 
 
-        initializeBean( pwmRequest, setupResponsesBean );
-
-        pwmRequest.setAttribute( PwmRequestAttribute.ModuleBean, setupResponsesBean );
-        pwmRequest.setAttribute( PwmRequestAttribute.ModuleBean_String, pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( setupResponsesBean ) );
-        pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_ResponseInfo, pwmRequest.getPwmSession().getUserInfo().getResponseInfoBean() );
-
         if ( setupResponsesBean.isHasExistingResponses() && !pwmRequest.getPwmSession().getUserInfo().isRequiresResponseConfig() )
         if ( setupResponsesBean.isHasExistingResponses() && !pwmRequest.getPwmSession().getUserInfo().isRequiresResponseConfig() )
         {
         {
-            pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES_EXISTING );
-            return;
-        }
-
-        if ( !setupResponsesBean.isResponsesSatisfied() )
-        {
-            final boolean allowskip = checkIfAllowSkipCr( pwmRequest );
-            pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_AllowSkip, allowskip );
-            pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES );
+            forwardToJsp( pwmRequest, JspUrl.SETUP_RESPONSES_EXISTING );
             return;
             return;
         }
         }
 
 
-        if ( !setupResponsesBean.isHelpdeskResponsesSatisfied() )
+        for ( final ResponseMode responseMode : ResponseMode.values() )
         {
         {
-            if ( setupResponsesBean.getHelpdeskResponseData().getChallengeSet() == null
-                    || setupResponsesBean.getHelpdeskResponseData().getChallengeSet().getChallenges().isEmpty() )
+            if ( !setupResponsesBean.getResponsesSatisfied().contains( responseMode )
+                    && SetupResponsesUtil.hasChallenges( pwmRequest, responseMode ) )
             {
             {
-                setupResponsesBean.setHelpdeskResponsesSatisfied( true );
-            }
-            else
-            {
-                pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES_HELPDESK );
+                pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_ChallengeSet, getChallengeSet( pwmRequest, responseMode ) );
+                pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_AllowSkip, SetupResponsesUtil.checkIfAllowSkipCr( pwmRequest ) );
+                pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_SetupData, setupResponsesBean.getChallengeData().get( responseMode ) );
+
+                forwardToJsp( pwmRequest, responseMode == ResponseMode.helpdesk
+                        ? JspUrl.SETUP_RESPONSES_HELPDESK
+                        : JspUrl.SETUP_RESPONSES );
                 return;
                 return;
             }
             }
         }
         }
 
 
-        if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.CHALLENGE_SHOW_CONFIRMATION ) )
+        final SetupResponsesProfile setupResponsesProfile = getSetupProfile( pwmRequest );
+        if ( setupResponsesProfile.readSettingAsBoolean( PwmSetting.SETUP_RESPONSES_SHOW_CONFIRMATION ) )
         {
         {
             if ( !setupResponsesBean.isConfirmed() )
             if ( !setupResponsesBean.isConfirmed() )
             {
             {
-                pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES_CONFIRM );
+                forwardToJsp( pwmRequest, JspUrl.SETUP_RESPONSES_CONFIRM );
                 return;
                 return;
             }
             }
         }
         }
@@ -367,11 +372,11 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         try
         try
         {
         {
             // everything good, so lets save responses.
             // everything good, so lets save responses.
-            final ResponseInfoBean responses = generateResponseInfoBean(
+            final ResponseInfoBean responses = SetupResponsesUtil.generateResponseInfoBean(
                     pwmRequest,
                     pwmRequest,
-                    setupResponsesBean.getResponseData().getChallengeSet(),
-                    setupResponsesBean.getResponseData().getResponseMap(),
-                    setupResponsesBean.getHelpdeskResponseData().getResponseMap()
+                    getChallengeSet( pwmRequest, ResponseMode.user ),
+                    setupResponsesBean.getChallengeData().get( ResponseMode.user ).getResponseMap(),
+                    setupResponsesBean.getChallengeData().get( ResponseMode.helpdesk ).getResponseMap()
             );
             );
             saveResponses( pwmRequest, responses );
             saveResponses( pwmRequest, responses );
             pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
             pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
@@ -390,17 +395,29 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         }
         }
     }
     }
 
 
+    private void forwardToJsp( final PwmRequest pwmRequest, final JspUrl jspUrl )
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
+
+        pwmRequest.setAttribute( PwmRequestAttribute.ModuleBean, setupResponsesBean );
+        pwmRequest.setAttribute( PwmRequestAttribute.ModuleBean_String, pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( setupResponsesBean ) );
+        pwmRequest.setAttribute( PwmRequestAttribute.SetupResponses_ResponseInfo, pwmRequest.getPwmSession().getUserInfo().getResponseInfoBean() );
+
+        pwmRequest.forwardToJsp( jspUrl );
+    }
+
 
 
     private void setupResponses(
     private void setupResponses(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
-            final boolean helpdeskMode
+            final ResponseMode responseMode
     )
     )
-            throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException
+            throws PwmUnrecoverableException
     {
     {
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
-        final SetupResponsesBean.SetupData setupData = helpdeskMode ? setupResponsesBean.getHelpdeskResponseData() : setupResponsesBean.getResponseData();
+        final SetupResponsesBean.SetupData setupData = setupResponsesBean.getChallengeData().get( responseMode );
 
 
-        final ChallengeSet challengeSet = setupData.getChallengeSet();
+        final ChallengeSet challengeSet = getChallengeProfile( pwmRequest ).getChallengeSet().orElseThrow();
         final Map<Challenge, String> responseMap;
         final Map<Challenge, String> responseMap;
         try
         try
         {
         {
@@ -413,22 +430,14 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         }
         }
         catch ( final PwmDataValidationException e )
         catch ( final PwmDataValidationException e )
         {
         {
-            LOGGER.debug( pwmRequest, () -> "error with new " + ( helpdeskMode ? "helpdesk" : "user" ) + " responses: " + e.getErrorInformation().toDebugStr() );
+            LOGGER.debug( pwmRequest, () -> "error with new " + responseMode.name()  + " responses: " + e.getErrorInformation().toDebugStr() );
             setLastError( pwmRequest, e.getErrorInformation() );
             setLastError( pwmRequest, e.getErrorInformation() );
             return;
             return;
         }
         }
 
 
-        LOGGER.trace( pwmRequest, () -> ( helpdeskMode ? "helpdesk" : "user" ) + " responses are acceptable" );
-        if ( helpdeskMode )
-        {
-            setupResponsesBean.getHelpdeskResponseData().setResponseMap( responseMap );
-            setupResponsesBean.setHelpdeskResponsesSatisfied( true );
-        }
-        else
-        {
-            setupResponsesBean.getResponseData().setResponseMap( responseMap );
-            setupResponsesBean.setResponsesSatisfied( true );
-        }
+        LOGGER.trace( pwmRequest, () -> responseMode.name() + " responses are acceptable" );
+        setupData.setResponseMap( responseMap );
+        setupResponsesBean.getResponsesSatisfied().add( responseMode );
     }
     }
 
 
     private void saveResponses( final PwmRequest pwmRequest, final ResponseInfoBean responseInfoBean )
     private void saveResponses( final PwmRequest pwmRequest, final ResponseInfoBean responseInfoBean )
@@ -452,7 +461,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             throws PwmDataValidationException, PwmUnrecoverableException
             throws PwmDataValidationException, PwmUnrecoverableException
     {
     {
         final Map<String, String> inputMap = pwmRequest.readParametersAsMap();
         final Map<String, String> inputMap = pwmRequest.readParametersAsMap();
-        return paramMapToChallengeMap( inputMap, setupData );
+        final ChallengeSet challengeSet = getChallengeProfile( pwmRequest ).getChallengeSet().orElseThrow();
+        return SetupResponsesUtil.paramMapToChallengeMap( challengeSet, inputMap, setupData );
     }
     }
 
 
     private static Map<Challenge, String> readResponsesFromJsonRequest(
     private static Map<Challenge, String> readResponsesFromJsonRequest(
@@ -462,171 +472,10 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             throws PwmDataValidationException, PwmUnrecoverableException, IOException
             throws PwmDataValidationException, PwmUnrecoverableException, IOException
     {
     {
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap();
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap();
-        return paramMapToChallengeMap( inputMap, setupData );
+        final ChallengeSet challengeSet = getChallengeProfile( pwmRequest ).getChallengeSet().orElseThrow();
+        return SetupResponsesUtil.paramMapToChallengeMap( challengeSet, inputMap, setupData );
     }
     }
 
 
-    private static Map<Challenge, String> paramMapToChallengeMap(
-            final Map<String, String> inputMap,
-            final SetupResponsesBean.SetupData setupData
-    )
-            throws PwmDataValidationException, PwmUnrecoverableException
-    {
-        //final SetupResponsesBean responsesBean = pwmSession.getSetupResponseBean();
-        final Map<Challenge, String> readResponses = new LinkedHashMap<>();
-
-        {
-            // read in the question texts and responses
-            for ( final String indexKey : setupData.getIndexedChallenges().keySet() )
-            {
-                final Challenge loopChallenge = setupData.getIndexedChallenges().get( indexKey );
-                if ( loopChallenge.isRequired() || !setupData.isSimpleMode() )
-                {
-
-                    final Challenge newChallenge;
-                    if ( !loopChallenge.isAdminDefined() )
-                    {
-                        final String questionText = inputMap.get( PwmConstants.PARAM_QUESTION_PREFIX + indexKey );
-                        newChallenge = new ChaiChallenge(
-                                loopChallenge.isRequired(),
-                                questionText,
-                                loopChallenge.getMinLength(),
-                                loopChallenge.getMaxLength(),
-                                loopChallenge.isAdminDefined(),
-                                loopChallenge.getMaxQuestionCharsInAnswer(),
-                                loopChallenge.isEnforceWordlist()
-                        );
-                    }
-                    else
-                    {
-                        newChallenge = loopChallenge;
-                    }
-
-                    final String answer = inputMap.get( PwmConstants.PARAM_RESPONSE_PREFIX + indexKey );
-
-                    if ( answer != null && answer.length() > 0 )
-                    {
-                        readResponses.put( newChallenge, answer );
-                    }
-                }
-            }
-
-            if ( setupData.isSimpleMode() )
-            {
-                // if in simple mode, read the select-based random challenges
-                for ( int i = 0; i < setupData.getIndexedChallenges().size(); i++ )
-                {
-                    final String questionText = inputMap.get( PwmConstants.PARAM_QUESTION_PREFIX + "Random_" + String.valueOf( i ) );
-
-                    Challenge challenge = null;
-                    for ( final Challenge loopC : setupData.getChallengeSet().getRandomChallenges() )
-                    {
-                        if ( loopC.isAdminDefined() && questionText != null && questionText.equals( loopC.getChallengeText() ) )
-                        {
-                            challenge = loopC;
-                            break;
-                        }
-                    }
-
-                    final String answer = inputMap.get( PwmConstants.PARAM_RESPONSE_PREFIX + "Random_" + String.valueOf( i ) );
-                    if ( answer != null && answer.length() > 0 )
-                    {
-                        readResponses.put( challenge, answer );
-                    }
-                }
-            }
-        }
-
-        return readResponses;
-    }
-
-
-    private static ResponseInfoBean generateResponseInfoBean(
-            final PwmRequest pwmRequest,
-            final ChallengeSet challengeSet,
-            final Map<Challenge, String> readResponses,
-            final Map<Challenge, String> helpdeskResponses
-    )
-            throws ChaiUnavailableException, PwmDataValidationException, PwmUnrecoverableException
-    {
-        final ChaiProvider provider = pwmRequest.getPwmSession().getSessionManager().getChaiProvider();
-
-        try
-        {
-            final ResponseInfoBean responseInfoBean = new ResponseInfoBean(
-                    readResponses,
-                    helpdeskResponses,
-                    challengeSet.getLocale(),
-                    challengeSet.getMinRandomRequired(),
-                    challengeSet.getIdentifier(),
-                    null,
-                    null
-            );
-
-            final ChaiResponseSet responseSet = ChaiCrFactory.newChaiResponseSet(
-                    readResponses,
-                    challengeSet.getLocale(),
-                    challengeSet.getMinRandomRequired(),
-                    provider.getChaiConfiguration(),
-                    challengeSet.getIdentifier() );
-
-            responseSet.meetsChallengeSetRequirements( challengeSet );
-
-            final SetupResponsesBean setupResponsesBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class );
-            final int minRandomRequiredSetup = setupResponsesBean.getResponseData().getMinRandomSetup();
-            if ( minRandomRequiredSetup == 0 )
-            {
-                // if using recover style, then all readResponseSet must be supplied at this point.
-                if ( responseSet.getChallengeSet().getRandomChallenges().size() < challengeSet.getRandomChallenges().size() )
-                {
-                    throw new ChaiValidationException( "too few random responses", ChaiError.CR_TOO_FEW_RANDOM_RESPONSES );
-                }
-            }
-
-            return responseInfoBean;
-        }
-        catch ( final ChaiValidationException e )
-        {
-            final ErrorInformation errorInfo = convertChaiValidationException( e );
-            throw new PwmDataValidationException( errorInfo );
-        }
-    }
-
-    private static ErrorInformation convertChaiValidationException(
-            final ChaiValidationException e
-    )
-    {
-        final String[] fieldNames = new String[] {
-                e.getFieldName(),
-        };
-
-        switch ( e.getErrorCode() )
-        {
-            case CR_TOO_FEW_CHALLENGES:
-                return new ErrorInformation( PwmError.ERROR_MISSING_REQUIRED_RESPONSE, null, fieldNames );
-
-            case CR_TOO_FEW_RANDOM_RESPONSES:
-                return new ErrorInformation( PwmError.ERROR_MISSING_RANDOM_RESPONSE, null, fieldNames );
-
-            case CR_MISSING_REQUIRED_CHALLENGE_TEXT:
-                return new ErrorInformation( PwmError.ERROR_MISSING_CHALLENGE_TEXT, null, fieldNames );
-
-            case CR_RESPONSE_TOO_LONG:
-                return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_LONG, null, fieldNames );
-
-            case CR_RESPONSE_TOO_SHORT:
-            case CR_MISSING_REQUIRED_RESPONSE_TEXT:
-                return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_SHORT, null, fieldNames );
-
-            case CR_DUPLICATE_RESPONSES:
-                return new ErrorInformation( PwmError.ERROR_RESPONSE_DUPLICATE, null, fieldNames );
-
-            case CR_TOO_MANY_QUESTION_CHARS:
-                return new ErrorInformation( PwmError.ERROR_CHALLENGE_IN_RESPONSE, null, fieldNames );
-
-            default:
-                return new ErrorInformation( PwmError.ERROR_INTERNAL );
-        }
-    }
 
 
     private void initializeBean(
     private void initializeBean(
             final PwmRequest pwmRequest,
             final PwmRequest pwmRequest,
@@ -634,121 +483,35 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        if ( pwmRequest.getPwmSession().getUserInfo().getResponseInfoBean() != null )
+        if ( setupResponsesBean.isInitialized() )
         {
         {
-            setupResponsesBean.setHasExistingResponses( true );
+            return;
         }
         }
 
 
-        final ChallengeProfile challengeProfile = pwmRequest.getPwmSession().getUserInfo().getChallengeProfile();
-        if ( setupResponsesBean.getResponseData() == null )
-        {
-            //setup user challenge data
-            final ChallengeSet userChallengeSet = challengeProfile.getChallengeSet();
-            final int minRandomSetup = challengeProfile.getMinRandomSetup();
-            final SetupResponsesBean.SetupData userSetupData = populateSetupData( userChallengeSet, minRandomSetup );
-            setupResponsesBean.setResponseData( userSetupData );
-        }
-        if ( setupResponsesBean.getHelpdeskResponseData() == null )
+        if ( pwmRequest.getPwmSession().getUserInfo().getResponseInfoBean() != null )
         {
         {
-            //setup helpdesk challenge data
-            final ChallengeSet helpdeskChallengeSet = challengeProfile.getHelpdeskChallengeSet();
-            if ( helpdeskChallengeSet == null )
-            {
-                setupResponsesBean.setHelpdeskResponseData( new SetupResponsesBean.SetupData() );
-            }
-            else
-            {
-                final int minRandomHelpdeskSetup = challengeProfile.getMinHelpdeskRandomsSetup();
-                final SetupResponsesBean.SetupData helpdeskSetupData = populateSetupData( helpdeskChallengeSet, minRandomHelpdeskSetup );
-                setupResponsesBean.setHelpdeskResponseData( helpdeskSetupData );
-            }
+            setupResponsesBean.setHasExistingResponses( true );
         }
         }
-    }
-
-    private static SetupResponsesBean.SetupData populateSetupData(
-            final ChallengeSet challengeSet,
-            final int minRandomSetup
-    )
-    {
-        boolean useSimple = true;
-        final Map<String, Challenge> indexedChallenges = new LinkedHashMap<>();
 
 
-        int minRandom = minRandomSetup;
+        final ChallengeProfile challengeProfile = pwmRequest.getPwmSession().getUserInfo().getChallengeProfile();
 
 
         {
         {
-            if ( minRandom != 0 && minRandom < challengeSet.getMinRandomRequired() )
-            {
-                minRandom = challengeSet.getMinRandomRequired();
-            }
-            if ( minRandom > challengeSet.getRandomChallenges().size() )
-            {
-                minRandom = 0;
-            }
-        }
-        {
-            {
-                if ( minRandom == 0 )
-                {
-                    useSimple = false;
-                }
-
-                for ( final Challenge challenge : challengeSet.getChallenges() )
-                {
-                    if ( !challenge.isRequired() && !challenge.isAdminDefined() )
-                    {
-                        useSimple = false;
-                    }
-                }
-
-                if ( challengeSet.getRandomChallenges().size() == challengeSet.getMinRandomRequired() )
-                {
-                    useSimple = false;
-                }
-            }
-        }
+            final SetupResponsesBean.SetupData userSetupData = challengeProfile.getChallengeSet()
+                    .map( challengeSet -> SetupResponsesUtil.populateSetupData( challengeSet, challengeProfile.getMinRandomSetup() ) )
+                    .orElse( new SetupResponsesBean.SetupData() );
 
 
-        {
-            int index = 0;
-            for ( final Challenge loopChallenge : challengeSet.getChallenges() )
-            {
-                indexedChallenges.put( String.valueOf( index ), loopChallenge );
-                index++;
-            }
+            setupResponsesBean.getChallengeData().put( ResponseMode.user, userSetupData );
         }
         }
 
 
-        final SetupResponsesBean.SetupData setupData = new SetupResponsesBean.SetupData();
-        setupData.setChallengeSet( challengeSet );
-        setupData.setSimpleMode( useSimple );
-        setupData.setIndexedChallenges( indexedChallenges );
-        setupData.setMinRandomSetup( minRandom );
-        return setupData;
-    }
-
-    @Value
-    private static class ValidationResponseBean implements Serializable
-    {
-        private String message;
-        private boolean success;
-    }
-
-    private static boolean checkIfAllowSkipCr( final PwmRequest pwmRequest )
-            throws PwmUnrecoverableException
-    {
-        if ( pwmRequest.isForcedPageView() )
         {
         {
-            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
-            if ( admin )
-            {
-                if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )
-                {
-                    LOGGER.trace( pwmRequest, () -> "allowing c/r answer setup skipping due to user being admin and setting "
-                            + PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES.toMenuLocationDebug( null, pwmRequest.getLocale() ) );
-                    return true;
-                }
-            }
+            final SetupResponsesBean.SetupData helpdeskSetupData = challengeProfile.getHelpdeskChallengeSet()
+                    .map( challengeSet -> SetupResponsesUtil.populateSetupData( challengeSet, challengeProfile.getMinHelpdeskRandomsSetup() ) )
+                    .orElse( new SetupResponsesBean.SetupData() );
+            setupResponsesBean.getChallengeData().put( ResponseMode.helpdesk, helpdeskSetupData );
         }
         }
 
 
-        return false;
+        setupResponsesBean.setInitialized( true );
     }
     }
+
 }
 }
 
 

+ 314 - 0
server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java

@@ -0,0 +1,314 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.http.servlet.setupresponses;
+
+import com.novell.ldapchai.cr.ChaiChallenge;
+import com.novell.ldapchai.cr.ChaiCrFactory;
+import com.novell.ldapchai.cr.ChaiResponseSet;
+import com.novell.ldapchai.cr.Challenge;
+import com.novell.ldapchai.cr.ChallengeSet;
+import com.novell.ldapchai.exception.ChaiError;
+import com.novell.ldapchai.exception.ChaiValidationException;
+import com.novell.ldapchai.provider.ChaiProvider;
+import password.pwm.Permission;
+import password.pwm.PwmConstants;
+import password.pwm.bean.ResponseInfoBean;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.ChallengeProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmDataValidationException;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.bean.SetupResponsesBean;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class SetupResponsesUtil
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SetupResponsesUtil.class );
+
+    public static boolean hasChallenges(
+            final PwmRequest pwmRequest,
+            final ResponseMode responseMode
+    )
+            throws PwmUnrecoverableException
+    {
+        final ChallengeProfile challengeProfile = SetupResponsesServlet.getChallengeProfile( pwmRequest );
+        if ( challengeProfile == null )
+        {
+            return false;
+        }
+
+        final Optional<ChallengeSet> optionalChallengeSet = responseMode == ResponseMode.helpdesk
+                ? challengeProfile.getHelpdeskChallengeSet()
+                : challengeProfile.getChallengeSet();
+
+        return optionalChallengeSet.isPresent() && !optionalChallengeSet.get().getChallenges().isEmpty();
+    }
+
+    static boolean checkIfAllowSkipCr( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.isForcedPageView() )
+        {
+            final boolean admin = pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmDomain(), Permission.PWMADMIN );
+            if ( admin )
+            {
+                if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES ) )
+                {
+                    LOGGER.trace( pwmRequest, () -> "allowing c/r answer setup skipping due to user being admin and setting "
+                            + PwmSetting.ADMIN_ALLOW_SKIP_FORCED_ACTIVITIES.toMenuLocationDebug( null, pwmRequest.getLocale() ) );
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    static Map<Challenge, String> paramMapToChallengeMap(
+            final ChallengeSet challengeSet,
+            final Map<String, String> inputMap,
+            final SetupResponsesBean.SetupData setupData
+    )
+            throws PwmDataValidationException, PwmUnrecoverableException
+    {
+        //final SetupResponsesBean responsesBean = pwmSession.getSetupResponseBean();
+        final Map<Challenge, String> readResponses = new LinkedHashMap<>();
+
+        {
+            // read in the question texts and responses
+            for ( final String indexKey : setupData.getIndexedChallenges().keySet() )
+            {
+                final Challenge loopChallenge = setupData.getIndexedChallenges().get( indexKey );
+                if ( loopChallenge.isRequired() || !setupData.isSimpleMode() )
+                {
+
+                    final Challenge newChallenge;
+                    if ( !loopChallenge.isAdminDefined() )
+                    {
+                        final String questionText = inputMap.get( PwmConstants.PARAM_QUESTION_PREFIX + indexKey );
+                        newChallenge = new ChaiChallenge(
+                                loopChallenge.isRequired(),
+                                questionText,
+                                loopChallenge.getMinLength(),
+                                loopChallenge.getMaxLength(),
+                                loopChallenge.isAdminDefined(),
+                                loopChallenge.getMaxQuestionCharsInAnswer(),
+                                loopChallenge.isEnforceWordlist()
+                        );
+                    }
+                    else
+                    {
+                        newChallenge = loopChallenge;
+                    }
+
+                    final String answer = inputMap.get( PwmConstants.PARAM_RESPONSE_PREFIX + indexKey );
+
+                    if ( answer != null && answer.length() > 0 )
+                    {
+                        readResponses.put( newChallenge, answer );
+                    }
+                }
+            }
+
+            if ( setupData.isSimpleMode() )
+            {
+                // if in simple mode, read the select-based random challenges
+                for ( int i = 0; i < setupData.getIndexedChallenges().size(); i++ )
+                {
+                    final String questionText = inputMap.get( PwmConstants.PARAM_QUESTION_PREFIX + "Random_" + String.valueOf( i ) );
+
+                    Challenge challenge = null;
+                    for ( final Challenge loopC : challengeSet.getRandomChallenges() )
+                    {
+                        if ( loopC.isAdminDefined() && questionText != null && questionText.equals( loopC.getChallengeText() ) )
+                        {
+                            challenge = loopC;
+                            break;
+                        }
+                    }
+
+                    final String answer = inputMap.get( PwmConstants.PARAM_RESPONSE_PREFIX + "Random_" + String.valueOf( i ) );
+                    if ( answer != null && answer.length() > 0 )
+                    {
+                        readResponses.put( challenge, answer );
+                    }
+                }
+            }
+        }
+
+        return readResponses;
+    }
+
+    static ResponseInfoBean generateResponseInfoBean(
+            final PwmRequest pwmRequest,
+            final ChallengeSet challengeSet,
+            final Map<Challenge, String> readResponses,
+            final Map<Challenge, String> helpdeskResponses
+    )
+            throws PwmDataValidationException, PwmUnrecoverableException
+    {
+        final ChaiProvider provider = pwmRequest.getPwmSession().getSessionManager().getChaiProvider();
+
+        try
+        {
+            final ResponseInfoBean responseInfoBean = new ResponseInfoBean(
+                    readResponses,
+                    helpdeskResponses,
+                    challengeSet != null ? challengeSet.getLocale() : null,
+                    challengeSet != null ? challengeSet.getMinRandomRequired() : 0,
+                    challengeSet != null ? challengeSet.getIdentifier() : null,
+                    null,
+                    null
+            );
+
+            if ( challengeSet != null )
+            {
+                final ChaiResponseSet responseSet = ChaiCrFactory.newChaiResponseSet(
+                        readResponses,
+                        challengeSet.getLocale(),
+                        challengeSet.getMinRandomRequired(),
+                        provider.getChaiConfiguration(),
+                        challengeSet.getIdentifier() );
+
+                responseSet.meetsChallengeSetRequirements( challengeSet );
+
+                final SetupResponsesBean setupResponsesBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, SetupResponsesBean.class );
+                final int minRandomRequiredSetup = setupResponsesBean.getChallengeData().get( ResponseMode.user ).getMinRandomSetup();
+                if ( minRandomRequiredSetup == 0 )
+                {
+                    // if using recover style, then all readResponseSet must be supplied at this point.
+                    if ( responseSet.getChallengeSet().getRandomChallenges().size() < challengeSet.getRandomChallenges().size() )
+                    {
+                        throw new ChaiValidationException( "too few random responses", ChaiError.CR_TOO_FEW_RANDOM_RESPONSES );
+                    }
+                }
+            }
+
+            return responseInfoBean;
+        }
+        catch ( final ChaiValidationException e )
+        {
+            final ErrorInformation errorInfo = convertChaiValidationException( e );
+            throw new PwmDataValidationException( errorInfo );
+        }
+    }
+
+    private static ErrorInformation convertChaiValidationException(
+            final ChaiValidationException e
+    )
+    {
+        final String[] fieldNames = new String[] {
+                e.getFieldName(),
+        };
+
+        switch ( e.getErrorCode() )
+        {
+            case CR_TOO_FEW_CHALLENGES:
+                return new ErrorInformation( PwmError.ERROR_MISSING_REQUIRED_RESPONSE, null, fieldNames );
+
+            case CR_TOO_FEW_RANDOM_RESPONSES:
+                return new ErrorInformation( PwmError.ERROR_MISSING_RANDOM_RESPONSE, null, fieldNames );
+
+            case CR_MISSING_REQUIRED_CHALLENGE_TEXT:
+                return new ErrorInformation( PwmError.ERROR_MISSING_CHALLENGE_TEXT, null, fieldNames );
+
+            case CR_RESPONSE_TOO_LONG:
+                return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_LONG, null, fieldNames );
+
+            case CR_RESPONSE_TOO_SHORT:
+            case CR_MISSING_REQUIRED_RESPONSE_TEXT:
+                return new ErrorInformation( PwmError.ERROR_RESPONSE_TOO_SHORT, null, fieldNames );
+
+            case CR_DUPLICATE_RESPONSES:
+                return new ErrorInformation( PwmError.ERROR_RESPONSE_DUPLICATE, null, fieldNames );
+
+            case CR_TOO_MANY_QUESTION_CHARS:
+                return new ErrorInformation( PwmError.ERROR_CHALLENGE_IN_RESPONSE, null, fieldNames );
+
+            default:
+                return new ErrorInformation( PwmError.ERROR_INTERNAL );
+        }
+    }
+
+    static SetupResponsesBean.SetupData populateSetupData(
+            final ChallengeSet challengeSet,
+            final int minRandomSetup
+    )
+    {
+        boolean useSimple = true;
+        final Map<String, Challenge> indexedChallenges = new LinkedHashMap<>();
+
+        int minRandom = minRandomSetup;
+
+        {
+            if ( minRandom != 0 && minRandom < challengeSet.getMinRandomRequired() )
+            {
+                minRandom = challengeSet.getMinRandomRequired();
+            }
+            if ( minRandom > challengeSet.getRandomChallenges().size() )
+            {
+                minRandom = 0;
+            }
+        }
+        {
+            {
+                if ( minRandom == 0 )
+                {
+                    useSimple = false;
+                }
+
+                for ( final Challenge challenge : challengeSet.getChallenges() )
+                {
+                    if ( !challenge.isRequired() && !challenge.isAdminDefined() )
+                    {
+                        useSimple = false;
+                    }
+                }
+
+                if ( challengeSet.getRandomChallenges().size() == challengeSet.getMinRandomRequired() )
+                {
+                    useSimple = false;
+                }
+            }
+        }
+
+        {
+            int index = 0;
+            for ( final Challenge loopChallenge : challengeSet.getChallenges() )
+            {
+                indexedChallenges.put( String.valueOf( index ), loopChallenge );
+                index++;
+            }
+        }
+
+        final SetupResponsesBean.SetupData setupData = new SetupResponsesBean.SetupData();
+        setupData.setSimpleMode( useSimple );
+        setupData.setIndexedChallenges( indexedChallenges );
+        setupData.setMinRandomSetup( minRandom );
+        return setupData;
+    }
+}

+ 33 - 0
server/src/main/java/password/pwm/http/servlet/setupresponses/ValidationResponseBean.java

@@ -0,0 +1,33 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package password.pwm.http.servlet.setupresponses;
+
+import lombok.Value;
+
+import java.io.Serializable;
+
+@Value
+public class ValidationResponseBean implements Serializable
+{
+    private String message;
+    private boolean success;
+}

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

@@ -256,7 +256,7 @@ public class UpdateProfileServlet extends ControlledPwmServlet
     private ProcessStatus processReset( final PwmRequest pwmRequest )
     private ProcessStatus processReset( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
-        final ResetAction resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetAction.class, ResetAction.exitProfileUpdate );
+        final ResetAction resetType = pwmRequest.readParameterAsEnum( PwmConstants.PARAM_RESET_TYPE, ResetAction.class ).orElse( ResetAction.exitProfileUpdate );
 
 
         switch ( resetType )
         switch ( resetType )
         {
         {

+ 18 - 21
server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java

@@ -90,7 +90,7 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
             return false;
             return false;
         }
         }
 
 
-        if ( cookieBean.getType() == PwmSessionBean.Type.AUTHENTICATED )
+        if ( cookieBean.getBeanType() == PwmSessionBean.BeanType.AUTHENTICATED )
         {
         {
             if ( cookieBean.getGuid() == null )
             if ( cookieBean.getGuid() == null )
             {
             {
@@ -106,7 +106,7 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
             }
             }
         }
         }
 
 
-        if ( cookieBean.getType() == PwmSessionBean.Type.PUBLIC )
+        if ( cookieBean.getBeanType() == PwmSessionBean.BeanType.PUBLIC )
         {
         {
             if ( cookieBean.getTimestamp() == null )
             if ( cookieBean.getTimestamp() == null )
             {
             {
@@ -130,32 +130,29 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
     @Override
     @Override
     public void saveSessionBeans( final PwmRequest pwmRequest )
     public void saveSessionBeans( final PwmRequest pwmRequest )
     {
     {
-        if ( pwmRequest == null || pwmRequest.getPwmResponse().isCommitted() )
+        if ( pwmRequest == null || pwmRequest.getPwmResponse().isCommitted() || pwmRequest.getPwmResponse() == null  )
         {
         {
             return;
             return;
         }
         }
         try
         try
         {
         {
-            if ( pwmRequest != null && pwmRequest.getPwmResponse() != null )
+            final Map<Class<? extends PwmSessionBean>, PwmSessionBean> beansInRequest = getRequestBeanMap( pwmRequest );
+            if ( beansInRequest != null )
             {
             {
-                final Map<Class<? extends PwmSessionBean>, PwmSessionBean> beansInRequest = getRequestBeanMap( pwmRequest );
-                if ( beansInRequest != null )
+                for ( final Map.Entry<Class<? extends PwmSessionBean>, PwmSessionBean> entry : beansInRequest.entrySet() )
                 {
                 {
-                    for ( final Map.Entry<Class<? extends PwmSessionBean>, PwmSessionBean> entry : beansInRequest.entrySet() )
+                    final Class<? extends PwmSessionBean> theClass = entry.getKey();
+                    final String cookieName = nameForClass( pwmRequest, theClass );
+                    final PwmSessionBean bean = entry.getValue();
+                    if ( bean == null )
                     {
                     {
-                        final Class<? extends PwmSessionBean> theClass = entry.getKey();
-                        final String cookieName = nameForClass( pwmRequest, theClass );
-                        final PwmSessionBean bean = entry.getValue();
-                        if ( bean == null )
-                        {
-                            pwmRequest.getPwmResponse().removeCookie( cookieName, COOKIE_PATH );
-                        }
-                        else
-                        {
-                            final PwmSecurityKey key = keyForSession( pwmRequest );
-                            final String encrytedValue = pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( entry.getValue(), key );
-                            pwmRequest.getPwmResponse().writeCookie( cookieName, encrytedValue, -1, COOKIE_PATH );
-                        }
+                        pwmRequest.getPwmResponse().removeCookie( cookieName, COOKIE_PATH );
+                    }
+                    else
+                    {
+                        final PwmSecurityKey key = keyForSession( pwmRequest );
+                        final String encryptedValue = pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( entry.getValue(), key );
+                        pwmRequest.getPwmResponse().writeCookie( cookieName, encryptedValue, -1, COOKIE_PATH );
                     }
                     }
                 }
                 }
             }
             }
@@ -189,7 +186,7 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService();
         final DomainSecureService domainSecureService = pwmRequest.getPwmDomain().getSecureService();
-        return "b-" + StringUtil.truncate( domainSecureService.hash( theClass.getName() ), 8 );
+        return "b-" + StringUtil.truncate( domainSecureService.ephemeralHmac( theClass.getName() ), 8 );
     }
     }
 
 
     @Override
     @Override

+ 26 - 6
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -31,7 +31,6 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileDefinition;
-import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthService;
 import password.pwm.health.HealthService;
 import password.pwm.health.HealthStatus;
 import password.pwm.health.HealthStatus;
@@ -42,6 +41,7 @@ import password.pwm.svc.PwmService;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 
 
 import java.util.Collections;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
@@ -61,10 +61,10 @@ public enum PwmIfTest
     showHeaderMenu( new ShowHeaderMenuTest() ),
     showHeaderMenu( new ShowHeaderMenuTest() ),
     showVersionHeader( new BooleanAppPropertyTest( AppProperty.HTTP_HEADER_SEND_XVERSION ) ),
     showVersionHeader( new BooleanAppPropertyTest( AppProperty.HTTP_HEADER_SEND_XVERSION ) ),
     permission( new BooleanPermissionTest() ),
     permission( new BooleanPermissionTest() ),
-    otpSetupEnabled( new SetupOTPEnabled() ),
+    otpSetupEnabled( new ProfileEnabled( ProfileDefinition.SetupOTPProfile ) ),
     hasStoredOtpTimestamp( new HasStoredOtpTimestamp() ),
     hasStoredOtpTimestamp( new HasStoredOtpTimestamp() ),
     hasCustomJavascript( new HasCustomJavascript() ),
     hasCustomJavascript( new HasCustomJavascript() ),
-    setupChallengeEnabled( new BooleanPwmSettingTest( PwmSetting.CHALLENGE_ENABLE ) ),
+    setupResponsesEnabled( new ProfileEnabled( ProfileDefinition.SetupResponsesProfile ) ),
     shortcutsEnabled( new BooleanPwmSettingTest( PwmSetting.SHORTCUT_ENABLE ) ),
     shortcutsEnabled( new BooleanPwmSettingTest( PwmSetting.SHORTCUT_ENABLE ) ),
     peopleSearchAvailable( new BooleanPwmSettingTest( PwmSetting.PEOPLE_SEARCH_ENABLE ), new ActorHasProfileTest( ProfileDefinition.PeopleSearch ) ),
     peopleSearchAvailable( new BooleanPwmSettingTest( PwmSetting.PEOPLE_SEARCH_ENABLE ), new ActorHasProfileTest( ProfileDefinition.PeopleSearch ) ),
     orgChartEnabled( new OrgChartEnabled() ),
     orgChartEnabled( new OrgChartEnabled() ),
@@ -516,8 +516,15 @@ public enum PwmIfTest
         }
         }
     }
     }
 
 
-    private static class SetupOTPEnabled implements Test
+    private static class ProfileEnabled implements Test
     {
     {
+        private final ProfileDefinition profileDefinition;
+
+        ProfileEnabled( final ProfileDefinition profileDefinition )
+        {
+            this.profileDefinition = Objects.requireNonNull( profileDefinition );
+        }
+
         @Override
         @Override
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
         {
@@ -526,8 +533,21 @@ public enum PwmIfTest
                 return false;
                 return false;
             }
             }
 
 
-            final SetupOtpProfile setupOtpProfile = pwmRequest.getSetupOTPProfile();
-            return setupOtpProfile != null && setupOtpProfile.readSettingAsBoolean( PwmSetting.OTP_ALLOW_SETUP );
+            if ( profileDefinition.getEnabledSetting().isPresent() )
+            {
+                if ( !pwmRequest.getDomainConfig().readSettingAsBoolean( profileDefinition.getEnabledSetting().get() ) )
+                {
+                    return false;
+                }
+            }
+
+            final String profileID = pwmRequest.getPwmSession().getUserInfo().getProfileIDs().get( profileDefinition );
+            if ( StringUtil.isEmpty( profileID ) )
+            {
+                return false;
+            }
+
+            return true;
         }
         }
     }
     }
 
 

+ 2 - 0
server/src/main/java/password/pwm/i18n/Display.java

@@ -87,6 +87,7 @@ public enum Display implements PwmDisplayBundle
     Display_CheckingResponses,
     Display_CheckingResponses,
     Display_CommunicationError,
     Display_CommunicationError,
     Display_ConfirmResponses,
     Display_ConfirmResponses,
+    Display_ConfirmHelpdeskResponses,
     Display_Day,
     Display_Day,
     Display_Days,
     Display_Days,
     Display_ErrorBody,
     Display_ErrorBody,
@@ -268,6 +269,7 @@ public enum Display implements PwmDisplayBundle
     Long_Title_UserEventHistory,
     Long_Title_UserEventHistory,
     Long_Title_UserInformation,
     Long_Title_UserInformation,
     Title_AnsweredQuestions,
     Title_AnsweredQuestions,
+    Title_AnsweredHelpdeskQuestions,
     Title_ActivateUser,
     Title_ActivateUser,
     Title_Admin,
     Title_Admin,
     Title_Application,
     Title_Application,

+ 2 - 2
server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java

@@ -696,11 +696,11 @@ public class LdapOperationsHelper
         );
         );
         configBuilder.setSetting(
         configBuilder.setSetting(
                 ChaiSetting.CR_ALLOW_DUPLICATE_RESPONSES,
                 ChaiSetting.CR_ALLOW_DUPLICATE_RESPONSES,
-                Boolean.toString( config.readSettingAsBoolean( PwmSetting.CHALLENGE_ALLOW_DUPLICATE_RESPONSES ) )
+                Boolean.toString( config.readSettingAsBoolean( PwmSetting.SETUP_RESPONSES_ALLOW_DUPLICATE_RESPONSES ) )
         );
         );
         configBuilder.setSetting(
         configBuilder.setSetting(
                 ChaiSetting.CR_CASE_INSENSITIVE,
                 ChaiSetting.CR_CASE_INSENSITIVE,
-                Boolean.toString( config.readSettingAsBoolean( PwmSetting.CHALLENGE_CASE_INSENSITIVE ) )
+                Boolean.toString( config.readSettingAsBoolean( PwmSetting.SETUP_RESPONSES_CASE_INSENSITIVE ) )
         );
         );
 
 
         {
         {

+ 2 - 2
server/src/main/java/password/pwm/ldap/UserInfoReader.java

@@ -415,7 +415,7 @@ public class UserInfoReader implements UserInfo
                     pwmDomain,
                     pwmDomain,
                     sessionLabel,
                     sessionLabel,
                     getUserIdentity(),
                     getUserIdentity(),
-                    selfCachedReference.getChallengeProfile().getChallengeSet(),
+                    selfCachedReference.getChallengeProfile().getChallengeSet().orElse( null ),
                     selfCachedReference.getResponseInfoBean() );
                     selfCachedReference.getResponseInfoBean() );
         }
         }
         catch ( final ChaiUnavailableException e )
         catch ( final ChaiUnavailableException e )
@@ -442,7 +442,7 @@ public class UserInfoReader implements UserInfo
             return false;
             return false;
         }
         }
 
 
-        if ( !setupOtpProfile.readSettingAsBoolean( PwmSetting.OTP_ALLOW_SETUP ) )
+        if ( !pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.OTP_ALLOW_SETUP ) )
         {
         {
             LOGGER.trace( sessionLabel, () -> "checkOtp: OTP allow setup is not enabled" );
             LOGGER.trace( sessionLabel, () -> "checkOtp: OTP allow setup is not enabled" );
             return false;
             return false;

+ 44 - 26
server/src/main/java/password/pwm/svc/cr/CrService.java

@@ -42,7 +42,10 @@ import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.ChallengeProfile;
+import password.pwm.config.profile.ProfileDefinition;
+import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmDataValidationException;
@@ -333,7 +336,6 @@ public class CrService extends AbstractPwmService implements PwmService
                 }
                 }
             }
             }
         }
         }
-
     }
     }
 
 
     private void checkResponsesAgainstWordlist( final Map<Challenge, String> responseMap )
     private void checkResponsesAgainstWordlist( final Map<Challenge, String> responseMap )
@@ -452,7 +454,7 @@ public class CrService extends AbstractPwmService implements PwmService
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final ChaiUser theUser
             final ChaiUser theUser
     )
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
     {
         final DomainConfig config = pwmDomain.getConfig();
         final DomainConfig config = pwmDomain.getConfig();
 
 
@@ -504,7 +506,6 @@ public class CrService extends AbstractPwmService implements PwmService
     )
     )
             throws PwmOperationalException, ChaiUnavailableException, ChaiValidationException
             throws PwmOperationalException, ChaiUnavailableException, ChaiValidationException
     {
     {
-
         int attempts = 0;
         int attempts = 0;
         int successes = 0;
         int successes = 0;
         final Map<DataStorageMethod, String> errorMessages = new LinkedHashMap<>();
         final Map<DataStorageMethod, String> errorMessages = new LinkedHashMap<>();
@@ -540,6 +541,14 @@ public class CrService extends AbstractPwmService implements PwmService
             throw new PwmOperationalException( errorInfo );
             throw new PwmOperationalException( errorInfo );
         }
         }
 
 
+        if ( successes == 0 )
+        {
+            final String errorMsg = "response storage unsuccessful; attempts=" + attempts + ", successes=" + successes
+                    + ", detail=" + JsonUtil.serializeMap( errorMessages );
+            final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, errorMsg );
+            throw new PwmOperationalException( errorInfo );
+        }
+
         if ( attempts != successes )
         if ( attempts != successes )
         {
         {
             final String errorMsg = "response storage only partially successful; attempts=" + attempts + ", successes=" + successes
             final String errorMsg = "response storage only partially successful; attempts=" + attempts + ", successes=" + successes
@@ -601,45 +610,55 @@ public class CrService extends AbstractPwmService implements PwmService
 
 
     public boolean checkIfResponseConfigNeeded(
     public boolean checkIfResponseConfigNeeded(
             final PwmDomain pwmDomain,
             final PwmDomain pwmDomain,
-            final SessionLabel pwmSession,
+            final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final ChallengeSet challengeSet,
             final ChallengeSet challengeSet,
             final ResponseInfoBean responseInfoBean
             final ResponseInfoBean responseInfoBean
     )
     )
             throws ChaiUnavailableException, PwmUnrecoverableException
             throws ChaiUnavailableException, PwmUnrecoverableException
     {
     {
-        LOGGER.trace( pwmSession, () -> "beginning check to determine if responses need to be configured for user" );
+        LOGGER.trace( sessionLabel, () -> "beginning check to determine if responses need to be configured for user" );
 
 
         final DomainConfig config = pwmDomain.getConfig();
         final DomainConfig config = pwmDomain.getConfig();
 
 
-        if ( !config.readSettingAsBoolean( PwmSetting.CHALLENGE_ENABLE ) )
-        {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: response setup is disabled, so user is not required to setup responses" );
-            return false;
-        }
-
-        if ( !config.readSettingAsBoolean( PwmSetting.CHALLENGE_FORCE_SETUP ) )
+        if ( !config.readSettingAsBoolean( PwmSetting.SETUP_RESPONSE_ENABLE ) )
         {
         {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: force response setup is disabled, so user is not required to setup responses" );
+            LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: response setup is disabled, so user is not required to setup responses" );
             return false;
             return false;
         }
         }
 
 
-        if ( !UserPermissionUtility.testUserPermission( pwmDomain, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_SETUP_RESPONSE ) ) )
         {
         {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have permission to setup responses" );
-            return false;
-        }
+            final Optional<String> profileId = ProfileUtility.discoverProfileIDForUser( pwmDomain, sessionLabel, userIdentity, ProfileDefinition.SetupResponsesProfile );
+            if ( profileId.isPresent() )
+            {
+                final SetupResponsesProfile setupResponsesProfile = pwmDomain.getConfig().getSetupResponseProfiles().get( profileId.get() );
+                if ( setupResponsesProfile != null )
+                {
+                    if ( !setupResponsesProfile.readSettingAsBoolean( PwmSetting.SETUP_RESPONSES_FORCE_SETUP ) )
+                    {
+                        LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: force response setup is disabled, so user is not required to setup responses" );
+                        return false;
+                    }
 
 
-        if ( !UserPermissionUtility.testUserPermission( pwmDomain, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_CHECK_RESPONSES ) ) )
-        {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " is not eligible for checkIfResponseConfigNeeded due to query match" );
-            return false;
+                    if ( !UserPermissionUtility.testUserPermission( pwmDomain, sessionLabel, userIdentity,
+                            setupResponsesProfile.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_CHECK_RESPONSES ) ) )
+                    {
+                        LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: " + userIdentity + " is not eligible for checkIfResponseConfigNeeded due to query match" );
+                        return false;
+                    }
+                }
+            }
+            else
+            {
+                LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have permission to setup responses" );
+                return false;
+            }
         }
         }
 
 
         // check to be sure there are actually challenges in the challenge set
         // check to be sure there are actually challenges in the challenge set
         if ( challengeSet == null || challengeSet.getChallenges().isEmpty() )
         if ( challengeSet == null || challengeSet.getChallenges().isEmpty() )
         {
         {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: no challenge sets configured for user " + userIdentity );
+            LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: no challenge sets configured for user " + userIdentity );
             return false;
             return false;
         }
         }
 
 
@@ -649,7 +668,7 @@ public class CrService extends AbstractPwmService implements PwmService
             final boolean ignoreNmasCr = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.NMAS_IGNORE_NMASCR_DURING_FORCECHECK ) );
             final boolean ignoreNmasCr = Boolean.parseBoolean( pwmDomain.getConfig().readAppProperty( AppProperty.NMAS_IGNORE_NMASCR_DURING_FORCECHECK ) );
             if ( ignoreNmasCr )
             if ( ignoreNmasCr )
             {
             {
-                LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: app property " + AppProperty.NMAS_IGNORE_NMASCR_DURING_FORCECHECK.getKey()
+                LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: app property " + AppProperty.NMAS_IGNORE_NMASCR_DURING_FORCECHECK.getKey()
                         + "=true and user's responses are in " + responseInfoBean.getDataStorageMethod() + " format, so forcing setup of new responses." );
                         + "=true and user's responses are in " + responseInfoBean.getDataStorageMethod() + " format, so forcing setup of new responses." );
                 return true;
                 return true;
             }
             }
@@ -665,13 +684,12 @@ public class CrService extends AbstractPwmService implements PwmService
 
 
             // check if responses meet the challenge set policy for the user
             // check if responses meet the challenge set policy for the user
             //usersResponses.meetsChallengeSetRequirements(challengeSet);
             //usersResponses.meetsChallengeSetRequirements(challengeSet);
-
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " has good responses" );
+            LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: " + userIdentity + " has good responses" );
             return false;
             return false;
         }
         }
         catch ( final Exception e )
         catch ( final Exception e )
         {
         {
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have good responses: " + e.getMessage() );
+            LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have good responses: " + e.getMessage() );
             return true;
             return true;
         }
         }
     }
     }

+ 18 - 0
server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java

@@ -36,6 +36,7 @@ import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.HmacAlgorithm;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmRandom;
@@ -62,6 +63,7 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
     protected PwmSecurityKey pwmSecurityKey;
     protected PwmSecurityKey pwmSecurityKey;
     private PwmBlockAlgorithm defaultBlockAlgorithm;
     private PwmBlockAlgorithm defaultBlockAlgorithm;
     private PwmHashAlgorithm defaultHashAlgorithm;
     private PwmHashAlgorithm defaultHashAlgorithm;
+    private HmacAlgorithm defaultHmacAlgorithm;
     private PwmRandom pwmRandom;
     private PwmRandom pwmRandom;
 
 
     private final StatisticCounterBundle<StatKey> stats = new StatisticCounterBundle<>( StatKey.class );
     private final StatisticCounterBundle<StatKey> stats = new StatisticCounterBundle<>( StatKey.class );
@@ -70,6 +72,8 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
     {
     {
         hashOperations,
         hashOperations,
         hashBytes,
         hashBytes,
+        hmacOperations,
+        hmacBytes,
         encryptOperations,
         encryptOperations,
         encryptBytes,
         encryptBytes,
         decryptOperations,
         decryptOperations,
@@ -101,6 +105,10 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
             final String defaultHashAlgString = pwmApplication.getConfig().readAppProperty( AppProperty.SECURITY_DEFAULT_EPHEMERAL_HASH_ALG );
             final String defaultHashAlgString = pwmApplication.getConfig().readAppProperty( AppProperty.SECURITY_DEFAULT_EPHEMERAL_HASH_ALG );
             defaultHashAlgorithm = JavaHelper.readEnumFromString( PwmHashAlgorithm.class, PwmHashAlgorithm.SHA512, defaultHashAlgString );
             defaultHashAlgorithm = JavaHelper.readEnumFromString( PwmHashAlgorithm.class, PwmHashAlgorithm.SHA512, defaultHashAlgString );
         }
         }
+        {
+            final String defaultHmacAlgString = pwmApplication.getConfig().readAppProperty( AppProperty.SECURITY_DEFAULT_EPHEMERAL_HMAC_ALG );
+            defaultHmacAlgorithm = JavaHelper.readEnumFromString( HmacAlgorithm.class, HmacAlgorithm.HMAC_SHA_512, defaultHmacAlgString );
+        }
         LOGGER.debug( getSessionLabel(), () -> "using default algorithms: " + StringUtil.mapToString( debugData() ) );
         LOGGER.debug( getSessionLabel(), () -> "using default algorithms: " + StringUtil.mapToString( debugData() ) );
 
 
         return STATUS.OPEN;
         return STATUS.OPEN;
@@ -226,6 +234,16 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         return SecureEngine.hash( input, defaultHashAlgorithm );
         return SecureEngine.hash( input, defaultHashAlgorithm );
     }
     }
 
 
+    public String ephemeralHmac(
+            final String input
+    )
+            throws PwmUnrecoverableException
+    {
+        stats.increment( StatKey.hmacOperations );
+        stats.increment( StatKey.hmacBytes, input.length() );
+        return SecureEngine.computeHmacToString( defaultHmacAlgorithm,  pwmSecurityKey, input );
+    }
+
     @Override
     @Override
     public String hash(
     public String hash(
             final PwmHashAlgorithm pwmHashAlgorithm,
             final PwmHashAlgorithm pwmHashAlgorithm,

+ 1 - 1
server/src/main/java/password/pwm/svc/token/TokenService.java

@@ -521,7 +521,7 @@ public class TokenService extends AbstractPwmService implements PwmService
             }
             }
         }
         }
 
 
-        if ( domainConfig.readSettingAsBoolean( PwmSetting.CHALLENGE_ENABLE ) )
+        if ( domainConfig.readSettingAsBoolean( PwmSetting.SETUP_RESPONSE_ENABLE ) )
         {
         {
             for ( final ForgottenPasswordProfile forgottenPasswordProfile : domainConfig.getForgottenPasswordProfiles().values() )
             for ( final ForgottenPasswordProfile forgottenPasswordProfile : domainConfig.getForgottenPasswordProfiles().values() )
             {
             {

+ 1 - 1
server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java

@@ -265,7 +265,7 @@ public class PropertyConfigurationImporter
         interestedProperties.add( PropertyKey.UA_ADMIN );
         interestedProperties.add( PropertyKey.UA_ADMIN );
         interestedProperties.add( PropertyKey.RPT_ADMIN );
         interestedProperties.add( PropertyKey.RPT_ADMIN );
 
 
-        final String filter = "( objectclass=* )";
+        final String filter = "(objectclass=*)";
         final List<UserPermission> permissions = new ArrayList<>( );
         final List<UserPermission> permissions = new ArrayList<>( );
 
 
         for ( final PropertyKey propertyKey : interestedProperties )
         for ( final PropertyKey propertyKey : interestedProperties )

+ 4 - 1
server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java

@@ -31,6 +31,8 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
@@ -75,7 +77,8 @@ public class ImportResponsesCommand extends AbstractCliCommand
                     {
                     {
                         final ChallengeProfile challengeProfile = pwmDomain.getCrService().readUserChallengeProfile(
                         final ChallengeProfile challengeProfile = pwmDomain.getCrService().readUserChallengeProfile(
                                 null, userIdentity, user, PwmPasswordPolicy.defaultPolicy(), PwmConstants.DEFAULT_LOCALE );
                                 null, userIdentity, user, PwmPasswordPolicy.defaultPolicy(), PwmConstants.DEFAULT_LOCALE );
-                        final ChallengeSet challengeSet = challengeProfile.getChallengeSet();
+                        final ChallengeSet challengeSet = challengeProfile.getChallengeSet()
+                                .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES.toInfo() ) );
                         final String userGuid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, null, userIdentity, false );
                         final String userGuid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, null, userIdentity, false );
                         final ResponseInfoBean responseInfoBean = inputData.toResponseInfoBean( PwmConstants.DEFAULT_LOCALE, challengeSet.getIdentifier() );
                         final ResponseInfoBean responseInfoBean = inputData.toResponseInfoBean( PwmConstants.DEFAULT_LOCALE, challengeSet.getIdentifier() );
                         pwmDomain.getCrService().writeResponses( null, userIdentity, user, userGuid, responseInfoBean );
                         pwmDomain.getCrService().writeResponses( null, userIdentity, user, userGuid, responseInfoBean );

+ 34 - 7
server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java

@@ -21,26 +21,32 @@
 package password.pwm.util.cli.commands;
 package password.pwm.util.cli.commands;
 
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
+import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenService;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
 
 
-import java.util.Collections;
+import java.util.List;
 
 
 public class TokenInfoCommand extends AbstractCliCommand
 public class TokenInfoCommand extends AbstractCliCommand
 {
 {
-    protected static final String TOKEN_KEY_OPTIONNAME = "token";
+    protected static final String TOKEN_KEY_OPTION_TOKEN = "token";
+    protected static final String TOKEN_KEY_OPTION_DOMAIN = "domain";
+
 
 
     @Override
     @Override
     public void doCommand( )
     public void doCommand( )
             throws Exception
             throws Exception
     {
     {
-        final String tokenKey = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTIONNAME );
+        final String tokenKey = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTION_TOKEN );
+        final String domainId = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTION_DOMAIN );
         final PwmApplication pwmApplication = cliEnvironment.getPwmApplication();
         final PwmApplication pwmApplication = cliEnvironment.getPwmApplication();
+        final PwmDomain pwmDomain = pwmApplication.domains().get( DomainID.create( domainId ) );
 
 
-        final TokenService tokenService = pwmApplication.getTokenService();
+        final TokenService tokenService = pwmDomain.getTokenService();
         TokenPayload tokenPayload = null;
         TokenPayload tokenPayload = null;
         Exception lookupError = null;
         Exception lookupError = null;
         try
         try
@@ -55,7 +61,7 @@ public class TokenInfoCommand extends AbstractCliCommand
         out( " token: " + tokenKey );
         out( " token: " + tokenKey );
         if ( lookupError != null )
         if ( lookupError != null )
         {
         {
-            out( "result: error during token lookup: " + lookupError.toString() );
+            out( "result: error during token lookup: " + lookupError );
         }
         }
         else if ( tokenPayload == null )
         else if ( tokenPayload == null )
         {
         {
@@ -96,14 +102,35 @@ public class TokenInfoCommand extends AbstractCliCommand
             @Override
             @Override
             public String getName( )
             public String getName( )
             {
             {
-                return TOKEN_KEY_OPTIONNAME;
+                return TOKEN_KEY_OPTION_TOKEN;
+            }
+        };
+
+        final CliParameters.Option domainId = new CliParameters.Option()
+        {
+            @Override
+            public boolean isOptional()
+            {
+                return false;
+            }
+
+            @Override
+            public Type getType()
+            {
+                return Type.STRING;
+            }
+
+            @Override
+            public String getName()
+            {
+                return TOKEN_KEY_OPTION_DOMAIN;
             }
             }
         };
         };
 
 
         final CliParameters cliParameters = new CliParameters();
         final CliParameters cliParameters = new CliParameters();
         cliParameters.commandName = "TokenInfo";
         cliParameters.commandName = "TokenInfo";
         cliParameters.description = "Get information about an issued token";
         cliParameters.description = "Get information about an issued token";
-        cliParameters.options = Collections.singletonList( tokenValue );
+        cliParameters.options = List.of( tokenValue, domainId );
         cliParameters.needsPwmApplication = true;
         cliParameters.needsPwmApplication = true;
         cliParameters.readOnly = false;
         cliParameters.readOnly = false;
         return cliParameters;
         return cliParameters;

+ 10 - 0
server/src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -341,6 +341,16 @@ public class SecureEngine
         return JavaHelper.byteArrayToHexString( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
         return JavaHelper.byteArrayToHexString( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
     }
     }
 
 
+    public static String computeHmacToString(
+            final HmacAlgorithm hmacAlgorithm,
+            final PwmSecurityKey pwmSecurityKey,
+            final String input
+    )
+            throws PwmUnrecoverableException
+    {
+        return JavaHelper.byteArrayToHexString( computeHmacToBytes( hmacAlgorithm, pwmSecurityKey, input.getBytes( PwmConstants.DEFAULT_CHARSET ) ) );
+    }
+
     public static byte[] computeHmacToBytes(
     public static byte[] computeHmacToBytes(
             final HmacAlgorithm hmacAlgorithm,
             final HmacAlgorithm hmacAlgorithm,
             final PwmSecurityKey pwmSecurityKey,
             final PwmSecurityKey pwmSecurityKey,

+ 11 - 11
server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java

@@ -23,7 +23,6 @@ package password.pwm.ws.server.rest;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.cr.ChaiChallenge;
 import com.novell.ldapchai.cr.ChaiChallenge;
 import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.Challenge;
-import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.bean.ChallengeBean;
 import com.novell.ldapchai.cr.bean.ChallengeBean;
 import com.novell.ldapchai.exception.ChaiException;
 import com.novell.ldapchai.exception.ChaiException;
@@ -44,9 +43,9 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.svc.cr.CrService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
-import password.pwm.svc.cr.CrService;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestRequest;
 import password.pwm.ws.server.RestRequest;
@@ -173,8 +172,6 @@ public class RestChallengesServer extends RestServlet
 
 
             // gather data
             // gather data
             final ResponseSet responseSet;
             final ResponseSet responseSet;
-            final ChallengeSet challengeSet;
-            final ChallengeSet helpdeskChallengeSet;
             final String outputUsername;
             final String outputUsername;
 
 
             final ChaiUser chaiUser = targetUserIdentity.getChaiUser();
             final ChaiUser chaiUser = targetUserIdentity.getChaiUser();
@@ -195,8 +192,6 @@ public class RestChallengesServer extends RestServlet
                     userLocale
                     userLocale
             );
             );
 
 
-            challengeSet = challengeProfile.getChallengeSet();
-            helpdeskChallengeSet = challengeProfile.getHelpdeskChallengeSet();
             outputUsername = targetUserIdentity.getUserIdentity().toDelimitedKey();
             outputUsername = targetUserIdentity.getUserIdentity().toDelimitedKey();
 
 
             // build output
             // build output
@@ -213,15 +208,18 @@ public class RestChallengesServer extends RestServlet
                     jsonData.minimumRandoms = responseSet.getChallengeSet().getMinRandomRequired();
                     jsonData.minimumRandoms = responseSet.getChallengeSet().getMinRandomRequired();
                 }
                 }
                 final Policy policy = new Policy();
                 final Policy policy = new Policy();
-                if ( challengeSet != null )
+
+                challengeProfile.getChallengeSet().ifPresent( challengeSet ->
                 {
                 {
                     policy.challenges = challengesToBeans( challengeSet.getChallenges() );
                     policy.challenges = challengesToBeans( challengeSet.getChallenges() );
                     policy.minimumRandoms = challengeSet.getMinRandomRequired();
                     policy.minimumRandoms = challengeSet.getMinRandomRequired();
-                }
-                if ( helpdeskChallengeSet != null && helpdesk )
+                } );
+
+                challengeProfile.getHelpdeskChallengeSet().ifPresent( helpdeskChallengeSet ->
                 {
                 {
                     policy.helpdeskChallenges = challengesToBeans( helpdeskChallengeSet.getChallenges() );
                     policy.helpdeskChallenges = challengesToBeans( helpdeskChallengeSet.getChallenges() );
-                }
+                } );
+
                 if ( policy.challenges != null || policy.helpdeskChallenges != null )
                 if ( policy.challenges != null || policy.helpdeskChallenges != null )
                 {
                 {
                     jsonData.policy = policy;
                     jsonData.policy = policy;
@@ -279,7 +277,9 @@ public class RestChallengesServer extends RestServlet
                     restRequest.getLocale()
                     restRequest.getLocale()
             );
             );
 
 
-            csIdentifer = challengeProfile.getChallengeSet().getIdentifier();
+            csIdentifer = challengeProfile.getChallengeSet()
+                    .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES.toInfo() ) )
+                    .getIdentifier();
 
 
             final ResponseInfoBean responseInfoBean = jsonInput.toResponseInfoBean( restRequest.getLocale(), csIdentifer );
             final ResponseInfoBean responseInfoBean = jsonInput.toResponseInfoBean( restRequest.getLocale(), csIdentifer );
             crService.writeResponses( restRequest.getSessionLabel(), userIdentity, chaiUser, userGUID, responseInfoBean );
             crService.writeResponses( restRequest.getSessionLabel(), userIdentity, chaiUser, userGUID, responseInfoBean );

+ 1 - 0
server/src/main/resources/password/pwm/AppProperty.properties

@@ -325,6 +325,7 @@ security.certs.allowSelfSigned=true
 security.certs.validateTimestamps=false
 security.certs.validateTimestamps=false
 security.defaultEphemeralBlockAlg=AES128_GCM
 security.defaultEphemeralBlockAlg=AES128_GCM
 security.defaultEphemeralHashAlg=SHA512
 security.defaultEphemeralHashAlg=SHA512
+security.defaultEphemeralHmacAlg=HmacSHA512
 security.config.minSecurityKeyLength=32
 security.config.minSecurityKeyLength=32
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 smtp.io.connectTimeoutMs=10000
 smtp.io.connectTimeoutMs=10000

+ 20 - 1
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -2120,11 +2120,25 @@
             <value>true</value>
             <value>true</value>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="true" key="setupResponses.profile.list" level="1" required="false">
+        <regex>^(?!.*all.*)([a-zA-Z][a-zA-Z0-9-]{2,15})$</regex>
+        <properties>
+            <property key="Minimum">1</property>
+        </properties>
+        <default>
+            <value>default</value>
+        </default>
+    </setting>
     <setting hidden="false" key="challenge.forceSetup" level="1" required="true">
     <setting hidden="false" key="challenge.forceSetup" level="1" required="true">
         <default>
         <default>
             <value>true</value>
             <value>true</value>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="true" key="challenge.helpdesk.forceSetup" level="1" required="true">
+        <default>
+            <value>true</value>
+        </default>
+    </setting>
     <setting hidden="false" key="challenge.randomChallenges" level="1">
     <setting hidden="false" key="challenge.randomChallenges" level="1">
         <default>
         <default>
             <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 the main character in your favorite book?","minLength":4,"maxLength":200,"adminDefined":true,enforceWordlist:true,maxQuestionCharsInAnswer:3}]]></value>
@@ -4243,7 +4257,12 @@
     </category>
     </category>
     <category hidden="false" scope="DOMAIN" key="PASSWORD_GLOBAL">
     <category hidden="false" scope="DOMAIN" key="PASSWORD_GLOBAL">
     </category>
     </category>
-    <category hidden="false" scope="DOMAIN" key="CHALLENGE">
+    <category hidden="false" scope="DOMAIN" key="SETUP_RESPONSES">
+    </category>
+    <category hidden="false" scope="DOMAIN" key="SETUP_RESPONSES_SETTINGS">
+    </category>
+    <category hidden="false" scope="DOMAIN" key="SETUP_RESPONSES_PROFILE" profiles="true">
+        <profile setting="setupResponses.profile.list"/>
     </category>
     </category>
     <category hidden="false" scope="DOMAIN" key="CHALLENGE_POLICY">
     <category hidden="false" scope="DOMAIN" key="CHALLENGE_POLICY">
         <profile setting="challenge.profile.list"/>
         <profile setting="challenge.profile.list"/>

+ 2 - 0
server/src/main/resources/password/pwm/i18n/Display.properties

@@ -99,6 +99,7 @@ Display_CheckingResponses=Checking Answers....
 Display_ClientDisconnect=Unable to communicate with server.
 Display_ClientDisconnect=Unable to communicate with server.
 Display_CommunicationError=Unable to communicate with server.  Continue when ready.
 Display_CommunicationError=Unable to communicate with server.  Continue when ready.
 Display_ConfirmResponses=Be sure your answers and questions are correct.  Check the spelling and punctuation.  In you are unable to remember your password, you will be able to access your account by supplying the answers to these security questions.
 Display_ConfirmResponses=Be sure your answers and questions are correct.  Check the spelling and punctuation.  In you are unable to remember your password, you will be able to access your account by supplying the answers to these security questions.
+Display_ConfirmHelpdeskResponses=These answers will allow your helpdesk to verify your identity when you contact them.  Be sure your answers and questions are correct. 
 Display_Day=day
 Display_Day=day
 Display_Days=days
 Display_Days=days
 Display_DeleteUserConfirm=Are you sure you wish to delete your account?  This can not be undone.
 Display_DeleteUserConfirm=Are you sure you wish to delete your account?  This can not be undone.
@@ -309,6 +310,7 @@ Long_Title_UserInformation=Information about your password and password policies
 Long_Title_DeleteAccount=Remove your account and profile from this service
 Long_Title_DeleteAccount=Remove your account and profile from this service
 Long_Title_VerificationSend=Before this user can be selected, the user's identity must be verified.  Please select a verification method.
 Long_Title_VerificationSend=Before this user can be selected, the user's identity must be verified.  Please select a verification method.
 Title_AnsweredQuestions=Answered Questions
 Title_AnsweredQuestions=Answered Questions
+Title_AnsweredHelpdeskQuestions=Answered Helpdesk Questions
 Title_ActivateUser=Activate Account
 Title_ActivateUser=Activate Account
 Title_Admin=Administration
 Title_Admin=Administration
 Title_Application=Password Self Service
 Title_Application=Password Self Service

+ 10 - 2
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -36,7 +36,9 @@ Category_Description_BASIC_SSO=Basic Authentication
 Category_Description_CAPTCHA=Captcha functionality uses an implementation of reCAPTCHA to prevent non-human attacks.  If this server faces the public internet, it is strongly recommended to enable the CAPTCHA functionality.  reCAPTCHA information can be found at <a href\="http\://www.google.com/recaptcha" target\="_blank">http\://www.google.com/recaptcha/</a><br/><br/>Registration at the reCAPTCHA site provides a site key and secret which you must configure here for reCAPTCHA support.
 Category_Description_CAPTCHA=Captcha functionality uses an implementation of reCAPTCHA to prevent non-human attacks.  If this server faces the public internet, it is strongly recommended to enable the CAPTCHA functionality.  reCAPTCHA information can be found at <a href\="http\://www.google.com/recaptcha" target\="_blank">http\://www.google.com/recaptcha/</a><br/><br/>Registration at the reCAPTCHA site provides a site key and secret which you must configure here for reCAPTCHA support.
 Category_Description_CAS_SSO=
 Category_Description_CAS_SSO=
 Category_Description_CHALLENGE_POLICY=Define the challenge policy users use for populating response answers.
 Category_Description_CHALLENGE_POLICY=Define the challenge policy users use for populating response answers.
-Category_Description_CHALLENGE=Settings that control the Challenge/Response features.  These global settings apply regardless of the challenge policy.  For profile-specific challenge settings, see Profiles --> Challenge Profiles.
+Category_Description_SETUP_RESPONSES=Settings that control the Challenge/Response features.  These global settings apply regardless of the challenge policy.  For profile-specific challenge settings, see Profiles --> Challenge Profiles.
+Category_Description_SETUP_RESPONSES_SETTINGS=Settings that control the Challenge/Response features.  These global settings apply regardless of the challenge policy.  For profile-specific challenge settings, see Profiles --> Challenge Profiles.
+Category_Description_SETUP_RESPONSES_PROFILE=Settings that control the Challenge/Response features.  These global settings apply regardless of the challenge policy.  For profile-specific challenge settings, see Profiles --> Challenge Profiles.
 Category_Description_CHANGE_PASSWORD=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
 Category_Description_CHANGE_PASSWORD=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
 Category_Description_CHANGE_PASSWORD_SETTINGS=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
 Category_Description_CHANGE_PASSWORD_SETTINGS=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
 Category_Description_CHANGE_PASSWORD_PROFILE=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
 Category_Description_CHANGE_PASSWORD_PROFILE=The change password module is the core functionality of the application.  Use these settings to control the behavior and functionality of the change password functionality that all users see.
@@ -149,7 +151,9 @@ Category_Label_BASIC_SSO=Basic Authentication
 Category_Label_CAPTCHA=Captcha
 Category_Label_CAPTCHA=Captcha
 Category_Label_CAS_SSO=CAS SSO
 Category_Label_CAS_SSO=CAS SSO
 Category_Label_CHALLENGE_POLICY=Challenge Policies
 Category_Label_CHALLENGE_POLICY=Challenge Policies
-Category_Label_CHALLENGE=Setup Security Questions
+Category_Label_SETUP_RESPONSES=Setup Security Questions
+Category_Label_SETUP_RESPONSES_SETTINGS=Setup Security Settings
+Category_Label_SETUP_RESPONSES_PROFILE=Setup Security Profiles
 Category_Label_CHANGE_PASSWORD=Change Password
 Category_Label_CHANGE_PASSWORD=Change Password
 Category_Label_CHANGE_PASSWORD_SETTINGS=Settings
 Category_Label_CHANGE_PASSWORD_SETTINGS=Settings
 Category_Label_CHANGE_PASSWORD_PROFILE=Profiles
 Category_Label_CHANGE_PASSWORD_PROFILE=Profiles
@@ -282,6 +286,7 @@ Setting_Description_challenge.caseInsensitive=Enable to control the case sensiti
 Setting_Description_challenge.enable=Enable this option to have the save responses page available to users. (Default enabled)
 Setting_Description_challenge.enable=Enable this option to have the save responses page available to users. (Default enabled)
 Setting_Description_challenge.enforceMinimumPasswordLifetime=Enable this option to enforce the minimum password lifetime setting when the users authenticate via Forgotten Password. If this setting is true, the users cannot change their passwords if the minimum password lifetime setting has not passed.  If false, @PwmAppName@ permits the users to change their passwords when they are authenticated via Forgotten Password even if the minimum lifetime setting has not passed.
 Setting_Description_challenge.enforceMinimumPasswordLifetime=Enable this option to enforce the minimum password lifetime setting when the users authenticate via Forgotten Password. If this setting is true, the users cannot change their passwords if the minimum password lifetime setting has not passed.  If false, @PwmAppName@ permits the users to change their passwords when they are authenticated via Forgotten Password even if the minimum lifetime setting has not passed.
 Setting_Description_challenge.forceSetup=Enable this option to direct the users to configure Challenge/Response when they log in.  @PwmAppName@ forces the users to enter responses if they do not have current valid responses stored.
 Setting_Description_challenge.forceSetup=Enable this option to direct the users to configure Challenge/Response when they log in.  @PwmAppName@ forces the users to enter responses if they do not have current valid responses stored.
+Setting_Description_challenge.helpdesk.forceSetup=Enable this option to direct the users to configure their helpdesk Challenge/Response when they log in.  @PwmAppName@ forces the users to enter responses if they do not have current valid responses stored.
 Setting_Description_challenge.helpdesk.minRandomsSetup=Specify the minimum number of Help Desk random questions you require the users to complete during the Response Setup.  If this number is higher than the available randoms, or lower than the minimum required, the system adjusts it accordingly.  Set this option to zero to force the users to configure all available randoms Challenge/Response questions at the time of setup.
 Setting_Description_challenge.helpdesk.minRandomsSetup=Specify the minimum number of Help Desk random questions you require the users to complete during the Response Setup.  If this number is higher than the available randoms, or lower than the minimum required, the system adjusts it accordingly.  Set this option to zero to force the users to configure all available randoms Challenge/Response questions at the time of setup.
 Setting_Description_challenge.helpdesk.randomChallenges=Specify additional random questions to present to the help desk users. @PwmAppName@ might require the users to supply answers to all or some of these questions when setting up their responses, as controlled by the "Minimum Help Desk Random Challenges Required During Setup" setting.  The questions and answers are visible to Help Desk users but are not used for forgotten password recovery.
 Setting_Description_challenge.helpdesk.randomChallenges=Specify additional random questions to present to the help desk users. @PwmAppName@ might require the users to supply answers to all or some of these questions when setting up their responses, as controlled by the "Minimum Help Desk Random Challenges Required During Setup" setting.  The questions and answers are visible to Help Desk users but are not used for forgotten password recovery.
 Setting_Description_challenge.helpdesk.requiredChallenges=Add the questions the users must supply answers for when setting up their responses.  The questions and answers are visible to Help Desk users but are not used for forgotten password recovery.
 Setting_Description_challenge.helpdesk.requiredChallenges=Add the questions the users must supply answers for when setting up their responses.  The questions and answers are visible to Help Desk users but are not used for forgotten password recovery.
@@ -557,6 +562,7 @@ Setting_Description_oauth.idserver.serverCerts=Import the certificate for the OA
 Setting_Description_otp.enabled=Enable this option to allow the user to configure and save an one time password.
 Setting_Description_otp.enabled=Enable this option to allow the user to configure and save an one time password.
 Setting_Description_otp.forceSetup=Enable this option and enabled one-time passwords to have @PwmAppName@ direct the user to configure a one-time password secret when logging in. @PwmAppName@ forces the user to configure one-time password if they do not have a current valid secret stored.
 Setting_Description_otp.forceSetup=Enable this option and enabled one-time passwords to have @PwmAppName@ direct the user to configure a one-time password secret when logging in. @PwmAppName@ forces the user to configure one-time password if they do not have a current valid secret stored.
 Setting_Description_otp.profile.list=List of OTP Profiles
 Setting_Description_otp.profile.list=List of OTP Profiles
+Setting_Description_setupResponses.profile.list=List of Setup Responses Profiles
 Setting_Description_otp.secret.allowSetup.queryMatch=Specify the set of users that this OTP Setup profile will include.
 Setting_Description_otp.secret.allowSetup.queryMatch=Specify the set of users that this OTP Setup profile will include.
 Setting_Description_otp.secret.encrypt=Enable this option to have @PwmAppName@ use the Security Key to encrypt and decrypt token information, to make sure it is not readable as plain text. Multiple application instances must use the same Security Key.  If you change the Security Key, stored OTP passwords are no longer usable.
 Setting_Description_otp.secret.encrypt=Enable this option to have @PwmAppName@ use the Security Key to encrypt and decrypt token information, to make sure it is not readable as plain text. Multiple application instances must use the same Security Key.  If you change the Security Key, stored OTP passwords are no longer usable.
 Setting_Description_otp.secret.identifier=Specify the User Identifier for OTP.  Macros are available.
 Setting_Description_otp.secret.identifier=Specify the User Identifier for OTP.  Macros are available.
@@ -821,6 +827,7 @@ Setting_Label_challenge.caseInsensitive=Case Insensitive Responses
 Setting_Label_challenge.enable=Enable Setup Responses
 Setting_Label_challenge.enable=Enable Setup Responses
 Setting_Label_challenge.enforceMinimumPasswordLifetime=Enforce Minimum Password Lifetime
 Setting_Label_challenge.enforceMinimumPasswordLifetime=Enforce Minimum Password Lifetime
 Setting_Label_challenge.forceSetup=Force Response Setup
 Setting_Label_challenge.forceSetup=Force Response Setup
+Setting_Label_challenge.helpdesk.forceSetup=Force Helpdesk Response Setup
 Setting_Label_challenge.helpdesk.minRandomsSetup=Minimum Help Desk Random Challenges Required During Setup
 Setting_Label_challenge.helpdesk.minRandomsSetup=Minimum Help Desk Random Challenges Required During Setup
 Setting_Label_challenge.helpdesk.randomChallenges=Help Desk Random Questions
 Setting_Label_challenge.helpdesk.randomChallenges=Help Desk Random Questions
 Setting_Label_challenge.helpdesk.requiredChallenges=Help Desk Required Questions
 Setting_Label_challenge.helpdesk.requiredChallenges=Help Desk Required Questions
@@ -1096,6 +1103,7 @@ Setting_Label_oauth.idserver.serverCerts=OAuth Server Certificate
 Setting_Label_otp.enabled=Allow Saving One Time Passwords
 Setting_Label_otp.enabled=Allow Saving One Time Passwords
 Setting_Label_otp.forceSetup=Force Setup of One Time Passwords
 Setting_Label_otp.forceSetup=Force Setup of One Time Passwords
 Setting_Label_otp.profile.list=OTP Profiles
 Setting_Label_otp.profile.list=OTP Profiles
+Setting_Label_setupResponses.profile.list=Setup Responses Profiles
 Setting_Label_otp.secret.allowSetup.queryMatch=One Time Password Profile Match
 Setting_Label_otp.secret.allowSetup.queryMatch=One Time Password Profile Match
 Setting_Label_otp.secret.encrypt=Encrypt OTP secret
 Setting_Label_otp.secret.encrypt=Encrypt OTP secret
 Setting_Label_otp.secret.identifier=OTP Secret Identifier
 Setting_Label_otp.secret.identifier=OTP Secret Identifier

+ 1 - 1
server/src/test/java/password/pwm/config/stored/StoredConfigKeyTest.java

@@ -74,7 +74,7 @@ public class StoredConfigKeyTest
         set.add( StoredConfigKey.forSetting( PwmSetting.PWM_SITE_URL, null, DomainID.systemId() ) );
         set.add( StoredConfigKey.forSetting( PwmSetting.PWM_SITE_URL, null, DomainID.systemId() ) );
         set.add( StoredConfigKey.forSetting( PwmSetting.SECURITY_ENABLE_FORM_NONCE, null, DomainID.systemId() ) );
         set.add( StoredConfigKey.forSetting( PwmSetting.SECURITY_ENABLE_FORM_NONCE, null, DomainID.systemId() ) );
         set.add( StoredConfigKey.forSetting( PwmSetting.SECURITY_ENABLE_FORM_NONCE, null, DomainID.systemId() ) );
         set.add( StoredConfigKey.forSetting( PwmSetting.SECURITY_ENABLE_FORM_NONCE, null, DomainID.systemId() ) );
-        set.add( StoredConfigKey.forSetting( PwmSetting.CHALLENGE_ENABLE, null, DomainID.systemId() ) );
+        set.add( StoredConfigKey.forSetting( PwmSetting.SETUP_RESPONSE_ENABLE, null, DomainID.systemId() ) );
         Assert.assertEquals( 3, set.size() );
         Assert.assertEquals( 3, set.size() );
     }
     }
 
 

+ 1 - 1
webapp/pom.xml

@@ -103,7 +103,7 @@
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-war-plugin</artifactId>
                 <artifactId>maven-war-plugin</artifactId>
-                <version>3.3.1</version>
+                <version>3.3.2</version>
                 <configuration>
                 <configuration>
                     <archiveClasses>false</archiveClasses>
                     <archiveClasses>false</archiveClasses>
                     <packagingExcludes>**/*.jsp</packagingExcludes>
                     <packagingExcludes>**/*.jsp</packagingExcludes>

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

@@ -458,12 +458,13 @@
             <% } %>
             <% } %>
         </table>
         </table>
         <br/>
         <br/>
+        <% { %><%-- Begin Challenge Profile --%>
         <table>
         <table>
             <tr>
             <tr>
                 <td colspan="10" class="title">Challenge Profile</td>
                 <td colspan="10" class="title">Challenge Profile</td>
             </tr>
             </tr>
             <% final ChallengeProfile challengeProfile = userDebugDataBean.getUserInfo().getChallengeProfile(); %>
             <% final ChallengeProfile challengeProfile = userDebugDataBean.getUserInfo().getChallengeProfile(); %>
-            <% if (challengeProfile == null) { %>
+            <% if ( challengeProfile == null ) { %>
             <tr>
             <tr>
                 <td>Assigned Profile</td>
                 <td>Assigned Profile</td>
                 <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
                 <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
@@ -494,7 +495,8 @@
                             <td class="key">Enforce Wordlist</td>
                             <td class="key">Enforce Wordlist</td>
                             <td class="key">Max Question Characters</td>
                             <td class="key">Max Question Characters</td>
                         </tr>
                         </tr>
-                        <% for (final Challenge challenge : challengeProfile.getChallengeSet().getChallenges()) { %>
+                        <% if ( challengeProfile.hasChallenges() ) { %>
+                        <% for (final Challenge challenge : challengeProfile.getChallengeSet().get().getChallenges()) { %>
                         <tr>
                         <tr>
                             <td>
                             <td>
                                 <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
                                 <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
@@ -519,13 +521,58 @@
                             </td>
                             </td>
                         </tr>
                         </tr>
                         <% } %>
                         <% } %>
+                        <% } %>
+                    </table>
+                </td>
+            </tr>
+            <tr>
+                <td>Helpdesk Challenges</td>
+                <td>
+                    <table>
+                        <tr>
+                            <td class="key">Type</td>
+                            <td class="key">Text</td>
+                            <td class="key">Required</td>
+                            <td class="key">Min Length</td>
+                            <td class="key">Max Length</td>
+                            <td class="key">Enforce Wordlist</td>
+                            <td class="key">Max Question Characters</td>
+                        </tr>
+                        <% if ( challengeProfile.hasHelpdeskChallenges() ) { %>
+                        <% for (final Challenge challenge : challengeProfile.getHelpdeskChallengeSet().get().getChallenges()) { %>
+                        <tr>
+                            <td>
+                                <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
+                            </td>
+                            <td>
+                                <%= JspUtility.friendlyWrite(pageContext, challenge.getChallengeText())%>
+                            </td>
+                            <td>
+                                <%= JspUtility.friendlyWrite(pageContext, challenge.isRequired())%>
+                            </td>
+                            <td>
+                                <%= challenge.getMinLength() %>
+                            </td>
+                            <td>
+                                <%= challenge.getMaxLength() %>
+                            </td>
+                            <td>
+                                <%= JspUtility.friendlyWrite(pageContext, challenge.isEnforceWordlist())%>
+                            </td>
+                            <td>
+                                <%= challenge.getMaxQuestionCharsInAnswer() %>
+                            </td>
+                        </tr>
+                        <% } %>
+                        <% } %>
                     </table>
                     </table>
                 </td>
                 </td>
             </tr>
             </tr>
             <% } %>
             <% } %>
         </table>
         </table>
-
+        <% } %><%-- End Challenge Profile --%>
         <% } %>
         <% } %>
+        </tr>
         <div class="buttonbar">
         <div class="buttonbar">
             <form method="get" class="pwm-form">
             <form method="get" class="pwm-form">
                 <button type="submit" class="btn"><pwm:display key="Button_Continue"/></button>
                 <button type="submit" class="btn"><pwm:display key="Button_Continue"/></button>

+ 9 - 5
webapp/src/main/webapp/WEB-INF/jsp/fragment/setupresponses-form.jsp

@@ -30,14 +30,18 @@
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="java.util.ArrayList" %>
 <%@ page import="java.util.ArrayList" %>
 <%@ page import="java.util.List" %>
 <%@ page import="java.util.List" %>
+<%@ page import="password.pwm.http.JspUtility" %>
+<%@ page import="password.pwm.http.PwmRequestAttribute" %>
+<%@ page import="com.novell.ldapchai.cr.ChallengeSet" %>
 
 
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%
 <%
-    final SetupResponsesBean.SetupData setupData = (SetupResponsesBean.SetupData)request.getAttribute("setupData");
+    final SetupResponsesBean.SetupData setupData = (SetupResponsesBean.SetupData) JspUtility.getPwmRequest( pageContext ).getAttribute( PwmRequestAttribute.SetupResponses_SetupData );
+    final ChallengeSet challengeSet = (ChallengeSet) JspUtility.getPwmRequest( pageContext ).getAttribute( PwmRequestAttribute.SetupResponses_ChallengeSet );
 %>
 %>
 <%-------------------------------- display fields for REQUIRED challenges ----------------------------------------------%>
 <%-------------------------------- display fields for REQUIRED challenges ----------------------------------------------%>
-<% if (!setupData.getChallengeSet().getRequiredChallenges().isEmpty()) { %>
+<% if (!challengeSet.getRequiredChallenges().isEmpty()) { %>
 <p><pwm:display key="Display_SetupRequiredResponses"/></p>
 <p><pwm:display key="Display_SetupRequiredResponses"/></p>
 <%
 <%
     for (final String indexKey : setupData.getIndexedChallenges().keySet()) {
     for (final String indexKey : setupData.getIndexedChallenges().keySet()) {
@@ -72,7 +76,7 @@
         }
         }
     }
     }
 %>
 %>
-<p><pwm:display key="Display_SetupRandomResponses" value1="<%= String.valueOf(setupData.getChallengeSet().getMinRandomRequired()) %>"/></p>
+<p><pwm:display key="Display_SetupRandomResponses" value1="<%= String.valueOf(challengeSet.getMinRandomRequired()) %>"/></p>
 <% for (int index = 0; index < setupData.getMinRandomSetup(); index++) { %>
 <% for (int index = 0; index < setupData.getMinRandomSetup(); index++) { %>
 <h2>
 <h2>
     <select name="PwmResponse_Q_Random_<%=index%>" id="PwmResponse_Q_Random_<%=index%>" style="width:70%" <pwm:autofocus/> class="simpleModeResponseSelection"
     <select name="PwmResponse_Q_Random_<%=index%>" id="PwmResponse_Q_Random_<%=index%>" style="width:70%" <pwm:autofocus/> class="simpleModeResponseSelection"
@@ -101,8 +105,8 @@
 </pwm:script>
 </pwm:script>
 <% } else { %>
 <% } else { %>
 <%---------------------- display fields for pwmRandom challenges using non-SIMPLE mode ----------------------------------------%>
 <%---------------------- display fields for pwmRandom challenges using non-SIMPLE mode ----------------------------------------%>
-<% if (!setupData.getChallengeSet().getRandomChallenges().isEmpty()) { %>
-<p><pwm:display key="Display_SetupRandomResponses" value1="<%= String.valueOf(setupData.getChallengeSet().getMinRandomRequired()) %>"/></p>
+<% if (!challengeSet.getRandomChallenges().isEmpty()) { %>
+<p><pwm:display key="Display_SetupRandomResponses" value1="<%= String.valueOf(challengeSet.getMinRandomRequired()) %>"/></p>
 <%
 <%
     for (final String indexKey : setupData.getIndexedChallenges().keySet()) {
     for (final String indexKey : setupData.getIndexedChallenges().keySet()) {
         final Challenge challenge = setupData.getIndexedChallenges().get(indexKey);
         final Challenge challenge = setupData.getIndexedChallenges().get(indexKey);

+ 19 - 14
webapp/src/main/webapp/WEB-INF/jsp/setupresponses-confirm.jsp

@@ -25,8 +25,12 @@
 
 
 <%@ page import="com.novell.ldapchai.cr.Challenge" %>
 <%@ page import="com.novell.ldapchai.cr.Challenge" %>
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
-<%@ page import="password.pwm.http.servlet.SetupResponsesServlet" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.SetupResponsesServlet" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
 <%@ page import="password.pwm.util.java.StringUtil" %>
+<%@ page import="password.pwm.http.PwmRequestAttribute" %>
+<%@ page import="java.util.Map" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.SetupResponsesUtil" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.ResponseMode" %>
 
 
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
@@ -41,34 +45,35 @@
     </jsp:include>
     </jsp:include>
     <div id="centerbody">
     <div id="centerbody">
         <h1 id="page-content-title"><pwm:display key="Title_ConfirmResponses" displayIfMissing="true"/></h1>
         <h1 id="page-content-title"><pwm:display key="Title_ConfirmResponses" displayIfMissing="true"/></h1>
-        <p><pwm:display key="Display_ConfirmResponses"/></p>
         <%@ include file="fragment/message.jsp" %>
         <%@ include file="fragment/message.jsp" %>
+        <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.user ) ) { %>
         <br/>
         <br/>
+        <p><pwm:display key="Display_ConfirmResponses"/></p>
         <%
         <%
-            for (final Challenge loopChallenge : responseBean.getResponseData().getResponseMap().keySet()) {
-                final String responseText = responseBean.getResponseData().getResponseMap().get(loopChallenge);
+            final Map<Challenge, String> responseMap = responseBean.getChallengeData().get( ResponseMode.user ).getResponseMap();
+            for (final Map.Entry<Challenge, String> entry : responseMap.entrySet() ) {
         %>
         %>
-        <h2><%= StringUtil.escapeHtml(loopChallenge.getChallengeText()) %>
-        </h2>
-
+        <h2><%= StringUtil.escapeHtml(entry.getKey().getChallengeText()) %></h2>
         <p>
         <p>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
-            <%= StringUtil.escapeHtml(responseText) %>
+            <%= StringUtil.escapeHtml(entry.getValue()) %>
         </p>
         </p>
         <% } %>
         <% } %>
+        <% } %>
+        <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.helpdesk ) ) { %>
         <br/>
         <br/>
+        <p><pwm:display key="Display_ConfirmHelpdeskResponses"/></p>
         <%
         <%
-            for (final Challenge loopChallenge : responseBean.getHelpdeskResponseData().getResponseMap().keySet()) {
-                final String responseText = responseBean.getHelpdeskResponseData().getResponseMap().get(loopChallenge);
+            final Map<Challenge, String> responseMap = responseBean.getChallengeData().get( ResponseMode.helpdesk ).getResponseMap();
+            for (final Map.Entry<Challenge, String> entry : responseMap.entrySet() ) {
         %>
         %>
-        <h2><%= StringUtil.escapeHtml(loopChallenge.getChallengeText()) %>
-        </h2>
-
+        <h2><%= StringUtil.escapeHtml(entry.getKey().getChallengeText()) %></h2>
         <p>
         <p>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
-            <%= StringUtil.escapeHtml(responseText) %>
+            <%= StringUtil.escapeHtml(entry.getValue()) %>
         </p>
         </p>
         <% } %>
         <% } %>
+        <% } %>
         <br/>
         <br/>
         <div class="buttonbar">
         <div class="buttonbar">
             <form style="display: inline" action="<pwm:current-url/>" method="post" name="changeResponses"
             <form style="display: inline" action="<pwm:current-url/>" method="post" name="changeResponses"

+ 10 - 2
webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp

@@ -42,7 +42,7 @@
     <div id="centerbody">
     <div id="centerbody">
         <h1 id="page-content-title"><pwm:display key="Title_ConfirmResponses" displayIfMissing="true"/></h1>
         <h1 id="page-content-title"><pwm:display key="Title_ConfirmResponses" displayIfMissing="true"/></h1>
         <p>
         <p>
-            <% if (responseInfoBean != null && responseInfoBean.getTimestamp() != null) { %>
+            <% if (responseInfoBean.getTimestamp() != null) { %>
             <pwm:display key="Display_WarnExistingResponseTime" value1="@ResponseSetupTime@"/>
             <pwm:display key="Display_WarnExistingResponseTime" value1="@ResponseSetupTime@"/>
             <% } else { %>
             <% } else { %>
             <pwm:display key="Display_WarnExistingResponse"/>
             <pwm:display key="Display_WarnExistingResponse"/>
@@ -50,9 +50,17 @@
         </p>
         </p>
         <%@ include file="fragment/message.jsp" %>
         <%@ include file="fragment/message.jsp" %>
         <br/>
         <br/>
+        <% if (responseInfoBean.getCrMap() != null && !responseInfoBean.getCrMap().isEmpty() ) { %>
         <h2><pwm:display key="Title_AnsweredQuestions"/></h2>
         <h2><pwm:display key="Title_AnsweredQuestions"/></h2>
         <% for (final Challenge loopChallenge : responseInfoBean.getCrMap().keySet()) { %>
         <% for (final Challenge loopChallenge : responseInfoBean.getCrMap().keySet()) { %>
-                <p><%= StringUtil.escapeHtml(loopChallenge.getChallengeText()) %></p>
+        <p><%= StringUtil.escapeHtml(loopChallenge.getChallengeText()) %></p>
+        <% } %>
+        <% } %>
+        <% if (responseInfoBean.getHelpdeskCrMap() != null && !responseInfoBean.getHelpdeskCrMap().isEmpty() ) { %>
+        <h2><pwm:display key="Title_AnsweredHelpdeskQuestions"/></h2>
+        <% for (final Challenge loopChallenge : responseInfoBean.getHelpdeskCrMap().keySet()) { %>
+        <p><%= StringUtil.escapeHtml(loopChallenge.getChallengeText()) %></p>
+        <% } %>
         <% } %>
         <% } %>
         <br/>
         <br/>
         <div class="buttonbar">
         <div class="buttonbar">

+ 8 - 4
webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp

@@ -26,13 +26,16 @@
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.tag.conditional.PwmIfTest" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
-<%@ page import="password.pwm.http.servlet.SetupResponsesServlet" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.SetupResponsesServlet" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.ResponseMode" %>
+<%@ page import="password.pwm.bean.ResponseInfoBean" %>
+<%@ page import="password.pwm.http.PwmResponse" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.SetupResponsesUtil" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 
 
 <%@ page language="java" session="true" isThreadSafe="true"
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
          contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<% final SetupResponsesBean responseBean = (SetupResponsesBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ModuleBean); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
 <body>
 <body>
@@ -47,7 +50,6 @@
               enctype="application/x-www-form-urlencoded" id="form-setupResponses" class="pwm-form" autocomplete="off">
               enctype="application/x-www-form-urlencoded" id="form-setupResponses" class="pwm-form" autocomplete="off">
             <%@ include file="fragment/message.jsp" %>
             <%@ include file="fragment/message.jsp" %>
             <div id="pwm-setupResponsesDiv">
             <div id="pwm-setupResponsesDiv">
-                <% request.setAttribute("setupData",responseBean.getHelpdeskResponseData()); %>
                 <jsp:include page="fragment/setupresponses-form.jsp"/>
                 <jsp:include page="fragment/setupresponses-form.jsp"/>
             </div>
             </div>
             <div class="buttonbar">
             <div class="buttonbar">
@@ -56,10 +58,12 @@
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-forward"></span></pwm:if>
                     <pwm:display key="Button_SetResponses"/>
                     <pwm:display key="Button_SetResponses"/>
                 </button>
                 </button>
+                <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.user ) ) { %>
                 <button type="submit" name="skip" class="btn" id="skipbutton" form="skipForm">
                 <button type="submit" name="skip" class="btn" id="skipbutton" form="skipForm">
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-backward"></span></pwm:if>
                     <pwm:if test="<%=PwmIfTest.showIcons%>"><span class="btn-icon pwm-icon pwm-icon-backward"></span></pwm:if>
                     <pwm:display key="Button_GoBack"/>
                     <pwm:display key="Button_GoBack"/>
                 </button>
                 </button>
+                <% } %>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
             </div>
             </div>
         </form>
         </form>
@@ -72,7 +76,7 @@
 </div>
 </div>
 <pwm:script>
 <pwm:script>
     <script type="text/javascript">
     <script type="text/javascript">
-        PWM_GLOBAL['responseMode'] = "helpdesk";
+        PWM_GLOBAL['responseMode'] = "<%=ResponseMode.helpdesk%>";
         PWM_GLOBAL['startupFunctions'].push(function(){
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_RESPONSES.startupResponsesPage();
             PWM_RESPONSES.startupResponsesPage();
         });
         });

+ 2 - 3
webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp

@@ -25,10 +25,10 @@
 
 
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
 <%@ page import="password.pwm.http.bean.SetupResponsesBean" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
 <%@ page import="password.pwm.http.PwmRequestAttribute" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.ResponseMode" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<% final SetupResponsesBean responseBean = (SetupResponsesBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ModuleBean); %>
 <% final boolean allowSkip = JspUtility.getBooleanAttribute( pageContext, PwmRequestAttribute.SetupResponses_AllowSkip ); %>
 <% final boolean allowSkip = JspUtility.getBooleanAttribute( pageContext, PwmRequestAttribute.SetupResponses_AllowSkip ); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <%@ include file="fragment/header.jsp" %>
@@ -43,7 +43,6 @@
         <form action="<pwm:current-url/>" method="post" name="form-setupResponses" enctype="application/x-www-form-urlencoded" id="form-setupResponses" class="pwm-form" autocomplete="off">
         <form action="<pwm:current-url/>" method="post" name="form-setupResponses" enctype="application/x-www-form-urlencoded" id="form-setupResponses" class="pwm-form" autocomplete="off">
             <%@ include file="fragment/message.jsp" %>
             <%@ include file="fragment/message.jsp" %>
             <div id="pwm-setupResponsesDiv">
             <div id="pwm-setupResponsesDiv">
-            <% request.setAttribute("setupData",responseBean.getResponseData()); %>
             <jsp:include page="fragment/setupresponses-form.jsp"/>
             <jsp:include page="fragment/setupresponses-form.jsp"/>
             </div>
             </div>
             <div class="buttonbar">
             <div class="buttonbar">
@@ -71,7 +70,7 @@
 </form>
 </form>
 <pwm:script>
 <pwm:script>
     <script type="text/javascript">
     <script type="text/javascript">
-        PWM_GLOBAL['responseMode'] = "user";
+        PWM_GLOBAL['responseMode'] = "<%=ResponseMode.user%>";
         PWM_GLOBAL['startupFunctions'].push(function(){
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_RESPONSES.startupResponsesPage();
             PWM_RESPONSES.startupResponsesPage();
         });
         });

+ 9 - 11
webapp/src/main/webapp/private/index.jsp

@@ -82,18 +82,16 @@
                 </a>
                 </a>
             </pwm:if>
             </pwm:if>
 
 
-            <pwm:if test="<%=PwmIfTest.setupChallengeEnabled%>">
-                <pwm:if test="<%=PwmIfTest.permission%>" permission="<%=Permission.SETUP_RESPONSE%>">
-                    <a id="button_SetupResponses" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupResponses.servletUrl()%>'/>">
-                        <div class="tile">
-                            <div class="tile-content">
-                                <div class="tile-image security-image"></div>
-                                <div class="tile-title" title="<pwm:display key='Title_SetupResponses'/>"><pwm:display key="Title_SetupResponses"/></div>
-                                <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupResponses'/>"><pwm:display key="Long_Title_SetupResponses"/></div>
-                            </div>
+            <pwm:if test="<%=PwmIfTest.setupResponsesEnabled%>">
+                <a id="button_SetupResponses" href="<pwm:url addContext="true" url='<%=PwmServletDefinition.SetupResponses.servletUrl()%>'/>">
+                    <div class="tile">
+                        <div class="tile-content">
+                            <div class="tile-image security-image"></div>
+                            <div class="tile-title" title="<pwm:display key='Title_SetupResponses'/>"><pwm:display key="Title_SetupResponses"/></div>
+                            <div class="tile-subtitle" title="<pwm:display key='Long_Title_SetupResponses'/>"><pwm:display key="Long_Title_SetupResponses"/></div>
                         </div>
                         </div>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
             </pwm:if>
             </pwm:if>
 
 
             <pwm:if test="<%=PwmIfTest.otpSetupEnabled%>">
             <pwm:if test="<%=PwmIfTest.otpSetupEnabled%>">

+ 4 - 1
webapp/src/main/webapp/public/resources/js/configeditor-settings-challenges.js

@@ -248,7 +248,10 @@ ChallengeSettingHandler.editLocale = function(keyName, localeKey) {
                     PWM_MAIN.JSLibrary.removeFromArray(PWM_VAR, 'tempValue');
                     PWM_MAIN.JSLibrary.removeFromArray(PWM_VAR, 'tempValue');
                     ChallengeSettingHandler.editLocale(keyName, localeKey);
                     ChallengeSettingHandler.editLocale(keyName, localeKey);
                 };
                 };
-                PWM_MAIN.showConfirmDialog({text:dialogText,loadFunction:loadFunction, okAction:okAction});
+                var cancelAction = function() {
+                    ChallengeSettingHandler.editLocale(keyName, localeKey);
+                }
+                PWM_MAIN.showConfirmDialog({text:dialogText,loadFunction:loadFunction, okAction:okAction, cancelAction:cancelAction});
             };
             };
 
 
             PWM_MAIN.addEventHandler('button-toggleWordlist-' + keyName + '-' + localeKey,'click',function(){
             PWM_MAIN.addEventHandler('button-toggleWordlist-' + keyName + '-' + localeKey,'click',function(){

Некоторые файлы не были показаны из-за большого количества измененных файлов