فهرست منبع

setup responses module improvements including support for multiple profiles

Jason Rivard 3 سال پیش
والد
کامیت
eb23f91ed5
64فایلهای تغییر یافته به همراه997 افزوده شده و 598 حذف شده
  1. 13 0
      CHANGES.md
  2. 1 0
      server/src/main/java/password/pwm/AppProperty.java
  3. 0 4
      server/src/main/java/password/pwm/Permission.java
  4. 6 0
      server/src/main/java/password/pwm/config/DomainConfig.java
  5. 18 14
      server/src/main/java/password/pwm/config/PwmSetting.java
  6. 3 1
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  7. 15 4
      server/src/main/java/password/pwm/config/profile/ChallengeProfile.java
  8. 32 17
      server/src/main/java/password/pwm/config/profile/ProfileDefinition.java
  9. 49 0
      server/src/main/java/password/pwm/config/profile/SetupResponsesProfile.java
  10. 5 0
      server/src/main/java/password/pwm/error/PwmInternalException.java
  11. 2 2
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  12. 6 0
      server/src/main/java/password/pwm/http/PwmRequest.java
  13. 2 0
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  14. 2 2
      server/src/main/java/password/pwm/http/bean/ActivateUserBean.java
  15. 2 2
      server/src/main/java/password/pwm/http/bean/AdminBean.java
  16. 2 2
      server/src/main/java/password/pwm/http/bean/ChangePasswordBean.java
  17. 2 2
      server/src/main/java/password/pwm/http/bean/ConfigGuideBean.java
  18. 2 2
      server/src/main/java/password/pwm/http/bean/ConfigManagerBean.java
  19. 2 2
      server/src/main/java/password/pwm/http/bean/DeleteAccountBean.java
  20. 2 2
      server/src/main/java/password/pwm/http/bean/ForgottenPasswordBean.java
  21. 2 2
      server/src/main/java/password/pwm/http/bean/GuestRegistrationBean.java
  22. 2 2
      server/src/main/java/password/pwm/http/bean/LoginServletBean.java
  23. 2 2
      server/src/main/java/password/pwm/http/bean/NewUserBean.java
  24. 2 2
      server/src/main/java/password/pwm/http/bean/PwmSessionBean.java
  25. 2 2
      server/src/main/java/password/pwm/http/bean/SetupOtpBean.java
  26. 8 9
      server/src/main/java/password/pwm/http/bean/SetupResponsesBean.java
  27. 2 2
      server/src/main/java/password/pwm/http/bean/ShortcutsBean.java
  28. 2 2
      server/src/main/java/password/pwm/http/bean/UpdateProfileBean.java
  29. 2 1
      server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  30. 1 1
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  31. 2 2
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  32. 1 1
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  33. 18 33
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  34. 1 1
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  35. 27 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/ResponseMode.java
  36. 122 359
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java
  37. 314 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesUtil.java
  38. 33 0
      server/src/main/java/password/pwm/http/servlet/setupresponses/ValidationResponseBean.java
  39. 1 1
      server/src/main/java/password/pwm/http/servlet/updateprofile/UpdateProfileServlet.java
  40. 18 21
      server/src/main/java/password/pwm/http/state/CryptoCookieBeanImpl.java
  41. 26 6
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  42. 2 0
      server/src/main/java/password/pwm/i18n/Display.java
  43. 2 2
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  44. 2 2
      server/src/main/java/password/pwm/ldap/UserInfoReader.java
  45. 44 26
      server/src/main/java/password/pwm/svc/cr/CrService.java
  46. 18 0
      server/src/main/java/password/pwm/svc/secure/AbstractSecureService.java
  47. 1 1
      server/src/main/java/password/pwm/svc/token/TokenService.java
  48. 4 1
      server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java
  49. 4 3
      server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java
  50. 10 0
      server/src/main/java/password/pwm/util/secure/SecureEngine.java
  51. 11 11
      server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java
  52. 1 0
      server/src/main/resources/password/pwm/AppProperty.properties
  53. 20 1
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  54. 2 0
      server/src/main/resources/password/pwm/i18n/Display.properties
  55. 10 2
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  56. 1 1
      server/src/test/java/password/pwm/config/stored/StoredConfigKeyTest.java
  57. 50 3
      webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp
  58. 9 5
      webapp/src/main/webapp/WEB-INF/jsp/fragment/setupresponses-form.jsp
  59. 19 14
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-confirm.jsp
  60. 10 2
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-existing.jsp
  61. 8 4
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses-helpdesk.jsp
  62. 2 3
      webapp/src/main/webapp/WEB-INF/jsp/setupresponses.jsp
  63. 9 11
      webapp/src/main/webapp/private/index.jsp
  64. 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

+ 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_DEFAULT_EPHEMERAL_BLOCK_ALG            ( "security.defaultEphemeralBlockAlg" ),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ( "security.defaultEphemeralHashAlg" ),
+    SECURITY_DEFAULT_EPHEMERAL_HMAC_ALG             ( "security.defaultEphemeralHmacAlg" ),
     SEEDLIST_BUILTIN_PATH                           ( "seedlist.builtin.path" ),
     SMTP_IO_CONNECT_TIMEOUT                         ( "smtp.io.connectTimeoutMs" ),
     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
 {
     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 ),
-    PEOPLE_SEARCH( PwmSetting.PEOPLE_SEARCH_QUERY_MATCH ),
-    PROFILE_UPDATE( PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
     WEBSERVICE( PwmSetting.WEBSERVICES_QUERY_MATCH ),
     WEBSERVICE_THIRDPARTY( PwmSetting.WEBSERVICES_THIRDPARTY_QUERY_MATCH ),;
 

+ 6 - 0
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.PwmPasswordPolicy;
 import password.pwm.config.profile.SetupOtpProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
@@ -344,6 +345,11 @@ public class DomainConfig implements SettingReader
         return this.getProfileMap( ProfileDefinition.SetupOTPProfile );
     }
 
+    public Map<String, SetupResponsesProfile> getSetupResponseProfiles( )
+    {
+        return this.getProfileMap( ProfileDefinition.SetupResponsesProfile );
+    }
+
     public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
     {
         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_SETUP_USER_PERMISSION(
             "otp.secret.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.OTP_PROFILE ),
-    OTP_ALLOW_SETUP(
-            "otp.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.OTP_PROFILE ),
     OTP_FORCE_SETUP(
             "otp.forceSetup", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_PROFILE ),
     OTP_SECRET_IDENTIFIER(
@@ -673,6 +671,8 @@ public enum PwmSetting
     OTP_RECOVERY_CODES(
             "otp.secret.recoveryCodes", PwmSettingSyntax.NUMERIC, PwmSettingCategory.OTP_PROFILE ),
 
+    OTP_ALLOW_SETUP(
+            "otp.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.OTP_SETTINGS ),
     OTP_SECRET_READ_PREFERENCE(
             "otp.secret.readPreference", PwmSettingSyntax.SELECT, PwmSettingCategory.OTP_SETTINGS ),
     OTP_SECRET_WRITE_PREFERENCE(
@@ -732,20 +732,24 @@ public enum PwmSetting
             "audit.syslog.outputFormat", PwmSettingSyntax.SELECT, PwmSettingCategory.AUDIT_FORWARD ),
 
     // 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(
-            "challenge.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.CHALLENGE ),
+            "challenge.allowSetup.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.SETUP_RESPONSES_PROFILE ),
     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_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_PROFILE( CHANGE_PASSWORD ),
 
-    CHALLENGE( MODULES_PRIVATE ),
+    SETUP_RESPONSES( MODULES_PRIVATE ),
+    SETUP_RESPONSES_SETTINGS( SETUP_RESPONSES ),
+    SETUP_RESPONSES_PROFILE( SETUP_RESPONSES ),
 
     RECOVERY( MODULES_PUBLIC ),
     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.List;
 import java.util.Locale;
+import java.util.Optional;
 
 public class ChallengeProfile implements Profile, Serializable
 {
@@ -154,14 +155,14 @@ public class ChallengeProfile implements Profile, Serializable
         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( )
@@ -265,4 +266,14 @@ public class ChallengeProfile implements Profile, Serializable
     {
         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.ChangePasswordProfileFactory.class,
             PwmSettingCategory.CHANGE_PASSWORD_PROFILE,
-            PwmSetting.QUERY_MATCH_CHANGE_PASSWORD ),
+            PwmSetting.QUERY_MATCH_CHANGE_PASSWORD, null ),
     AccountInformation(
             Type.AUTHENTICATED,
             AccountInformationProfile.class,
             AccountInformationProfile.AccountInformationProfileFactory.class,
             PwmSettingCategory.ACCOUNT_INFO_PROFILE,
-            PwmSetting.ACCOUNT_INFORMATION_QUERY_MATCH ),
+            PwmSetting.ACCOUNT_INFORMATION_QUERY_MATCH, null ),
     Helpdesk(
             Type.AUTHENTICATED,
             HelpdeskProfile.class,
             HelpdeskProfile.HelpdeskProfileFactory.class,
             PwmSettingCategory.HELPDESK_PROFILE,
-            PwmSetting.HELPDESK_PROFILE_QUERY_MATCH ),
+            PwmSetting.HELPDESK_PROFILE_QUERY_MATCH, null ),
     ForgottenPassword(
             Type.PUBLIC,
             ForgottenPasswordProfile.class,
             ForgottenPasswordProfile.ForgottenPasswordProfileFactory.class,
             PwmSettingCategory.RECOVERY_PROFILE,
-            PwmSetting.RECOVERY_PROFILE_QUERY_MATCH ),
+            PwmSetting.RECOVERY_PROFILE_QUERY_MATCH, null ),
     NewUser(
             Type.PUBLIC,
             NewUserProfile.class,
             NewUserProfile.NewUserProfileFactory.class,
             PwmSettingCategory.NEWUSER_PROFILE,
-            null ),
+            null, null ),
     UpdateAttributes(
             Type.AUTHENTICATED,
             UpdateProfileProfile.class,
             UpdateProfileProfile.UpdateProfileProfileFactory.class,
             PwmSettingCategory.UPDATE_PROFILE,
-            PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
+            PwmSetting.UPDATE_PROFILE_QUERY_MATCH, null ),
     ActivateUser(
             Type.PUBLIC,
             ActivateUserProfile.class,
             ActivateUserProfile.UserActivationProfileFactory.class,
             PwmSettingCategory.ACTIVATION_PROFILE,
-            PwmSetting.ACTIVATE_USER_QUERY_MATCH ),
+            PwmSetting.ACTIVATE_USER_QUERY_MATCH, null ),
     DeleteAccount(
             Type.AUTHENTICATED,
             DeleteAccountProfile.class,
             DeleteAccountProfile.DeleteAccountProfileFactory.class,
             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(
             Type.AUTHENTICATED,
             SetupOtpProfile.class,
             SetupOtpProfile.SetupOtpProfileFactory.class,
             PwmSettingCategory.OTP_PROFILE,
-            PwmSetting.OTP_SETUP_USER_PERMISSION ),
+            PwmSetting.OTP_SETUP_USER_PERMISSION,
+            PwmSetting.OTP_ALLOW_SETUP ),
     PeopleSearch(
             Type.AUTHENTICATED,
             PeopleSearchProfile.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
-            PwmSetting.PEOPLE_SEARCH_QUERY_MATCH ),
+            PwmSetting.PEOPLE_SEARCH_QUERY_MATCH, null ),
     PeopleSearchPublic(
             Type.PUBLIC,
             PeopleSearchProfile.class,
             PeopleSearchProfile.PeopleSearchProfileFactory.class,
             PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
-            null ),
+            null, null ),
     EmailServers(
             Type.SERVICE,
             EmailServerProfile.class,
             EmailServerProfile.EmailServerProfileFactory.class,
             PwmSettingCategory.EMAIL_SERVERS,
-            null ),
+            null, null ),
     PasswordPolicy(
             Type.SERVICE,
             PwmPasswordPolicy.class,
             null,
             PwmSettingCategory.PASSWORD_POLICY,
-            PwmSetting.PASSWORD_POLICY_QUERY_MATCH ),
+            PwmSetting.PASSWORD_POLICY_QUERY_MATCH, null ),
     LdapProfile(
             Type.SERVICE,
             LdapProfile.class,
             LdapProfile.LdapProfileFactory.class,
             PwmSettingCategory.LDAP_PROFILE,
-            null ),
+            null, null ),
     ChallengeProfile(
             Type.SERVICE,
             ChallengeProfile.class,
             null,
             PwmSettingCategory.CHALLENGE_POLICY,
-            PwmSetting.CHALLENGE_POLICY_QUERY_MATCH ),;
+            PwmSetting.CHALLENGE_POLICY_QUERY_MATCH, null ),;
 
     private final Type type;
     private final Class<? extends Profile> profileImplClass;
     private final Class<? extends Profile.ProfileFactory> profileFactoryClass;
     private final PwmSettingCategory category;
     private final PwmSetting queryMatch;
+    private final PwmSetting enabledSetting;
 
     enum Type
     {
@@ -136,14 +145,15 @@ public enum ProfileDefinition
             final Class<? extends Profile> profileImplClass,
             final Class<? extends Profile.ProfileFactory> profileFactoryClass,
             final PwmSettingCategory category,
-            final PwmSetting queryMatch
-    )
+            final PwmSetting queryMatch,
+            final PwmSetting enabledSetting )
     {
         this.type = type;
         this.profileImplClass = profileImplClass;
         this.profileFactoryClass = profileFactoryClass;
         this.category = category;
         this.queryMatch = queryMatch;
+        this.enabledSetting = enabledSetting;
     }
 
     public boolean isAuthenticated( )
@@ -170,4 +180,9 @@ public enum ProfileDefinition
     {
         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 );
     }
+
+    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 );
     }
 
-    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
     {
         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 )

+ 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.ProfileDefinition;
 import password.pwm.config.profile.SetupOtpProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
@@ -683,6 +684,11 @@ public class PwmRequest extends PwmHttpRequestWrapper
         return ( SetupOtpProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupOTPProfile );
     }
 
+    public SetupResponsesProfile getSetupResponsesProfile() throws PwmUnrecoverableException
+    {
+        return ( SetupResponsesProfile ) getProfile( getPwmDomain(), ProfileDefinition.SetupResponsesProfile );
+    }
+
     public UpdateProfileProfile getUpdateAttributeProfile() throws PwmUnrecoverableException
     {
         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,
 
     SetupResponses_ResponseInfo,
+    SetupResponses_ChallengeSet,
+    SetupResponses_SetupData,
     SetupResponses_AllowSkip,
 
     SetupOtp_QrCodeValue,

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

@@ -57,9 +57,9 @@ public class ActivateUserBean extends PwmSessionBean
 
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
 
     @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;
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
 
     @Override

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

@@ -153,9 +153,9 @@ public class ChangePasswordBean extends PwmSessionBean
     }
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
 
     @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;
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
 
     @Override

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

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

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

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

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

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

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

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

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

@@ -43,9 +43,9 @@ public class LoginServletBean extends PwmSessionBean
     }
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
 
     @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;
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.PUBLIC;
+        return BeanType.PUBLIC;
     }
 
     @Override

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

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

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

@@ -21,13 +21,15 @@
 package password.pwm.http.bean;
 
 import com.novell.ldapchai.cr.Challenge;
-import com.novell.ldapchai.cr.ChallengeSet;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import password.pwm.config.option.SessionBeanMode;
+import password.pwm.http.servlet.setupresponses.ResponseMode;
 
 import java.io.Serializable;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
@@ -37,25 +39,22 @@ import java.util.Set;
 public class SetupResponsesBean extends PwmSessionBean
 {
     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 Locale userLocale;
     private boolean initialized;
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
 
     @Data
     @EqualsAndHashCode( callSuper = false )
     public static class SetupData implements Serializable
     {
-        private ChallengeSet challengeSet;
         private Map<String, Challenge> indexedChallenges = Collections.emptyMap();
         private boolean simpleMode;
         private int minRandomSetup;
@@ -65,6 +64,6 @@ public class SetupResponsesBean extends PwmSessionBean
     @Override
     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
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
 
     @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;
 
     @Override
-    public Type getType( )
+    public BeanType getBeanType( )
     {
-        return Type.AUTHENTICATED;
+        return BeanType.AUTHENTICATED;
     }
 
     @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.peoplesearch.PrivatePeopleSearchServlet;
 import password.pwm.http.servlet.peoplesearch.PublicPeopleSearchServlet;
+import password.pwm.http.servlet.setupresponses.SetupResponsesServlet;
 import password.pwm.http.servlet.updateprofile.UpdateProfileServlet;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
@@ -77,7 +78,7 @@ public enum PwmServletDefinition
 
     AccountInformation( AccountInformationServlet.class, null ),
     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 ),
     SetupOtp( password.pwm.http.servlet.SetupOtpServlet.class, SetupOtpBean.class, Flag.RequiresUserPasswordAndBind ),
     Helpdesk( password.pwm.http.servlet.helpdesk.HelpdeskServlet.class, null ),

+ 1 - 1
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 )
             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 )
         {

+ 2 - 2
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 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 );
                 } );
 
 

+ 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 )
             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 )
         {

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

@@ -133,7 +133,7 @@ public class ForgottenPasswordUtil
         return Collections.unmodifiableSet( result );
     }
 
-    static Optional<UserInfo> readUserInfo(
+    public static Optional<UserInfo> readUserInfo(
             final PwmRequestContext pwmRequestContext,
             final ForgottenPasswordBean forgottenPasswordBean
     )
@@ -167,23 +167,12 @@ public class ForgottenPasswordUtil
 
         final PwmDomain pwmDomain = pwmRequestContext.getPwmDomain();
         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(
@@ -322,7 +311,8 @@ public class ForgottenPasswordUtil
                     throw new PwmUnrecoverableException( errorInformation );
                 }
 
-                final ChallengeSet challengeSet = userInfo.getChallengeProfile().getChallengeSet();
+                final ChallengeSet challengeSet = userInfo.getChallengeProfile().getChallengeSet()
+                        .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES ) );
 
                 try
                 {
@@ -525,7 +515,7 @@ public class ForgottenPasswordUtil
                     PwmError.ERROR_INTERNAL,
                     "unexpected ldap error while processing recovery action " + recoveryAction + ", error: " + e.getMessage()
             );
-            LOGGER.warn( pwmRequest, () -> errorInformation.toDebugStr() );
+            LOGGER.warn( pwmRequest, errorInformation::toDebugStr );
             pwmRequest.respondWithError( errorInformation );
         }
         finally
@@ -549,7 +539,8 @@ public class ForgottenPasswordUtil
         final List<Challenge> challengeList;
         {
             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() );
             for ( int i = 0; i < challengeSet.getMinRandomRequired(); i++ )
             {
@@ -596,7 +587,6 @@ public class ForgottenPasswordUtil
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity
     )
-            throws PwmUnrecoverableException
     {
         ForgottenPasswordProfile forgottenPasswordProfile = null;
         try
@@ -634,9 +624,9 @@ public class ForgottenPasswordUtil
     {
         final Optional<String> profileID = ProfileUtility.discoverProfileIDForUser(
                 pwmDomain,
-            sessionLabel,
-            userIdentity,
-            ProfileDefinition.ForgottenPassword
+                sessionLabel,
+                userIdentity,
+                ProfileDefinition.ForgottenPassword
         );
 
         if ( profileID.isPresent() )
@@ -712,10 +702,6 @@ public class ForgottenPasswordUtil
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
                 throw new PwmUnrecoverableException( errorInformation );
             }
-            catch ( final ChaiUnavailableException e )
-            {
-                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
-            }
         }
         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.setPresentableChallengeSet( challengeSet == null ? null : challengeSet.asChallengeSetBean() );
@@ -764,8 +750,7 @@ public class ForgottenPasswordUtil
     static List<FormConfiguration> figureAttributeForm(
             final ForgottenPasswordProfile forgottenPasswordProfile,
             final ForgottenPasswordBean forgottenPasswordBean,
-            final PwmRequestContext pwmRequestContext,
-            final UserIdentity userIdentity
+            final PwmRequestContext pwmRequestContext
     )
             throws PwmOperationalException, PwmUnrecoverableException
     {
@@ -839,13 +824,13 @@ public class ForgottenPasswordUtil
             final IdentityVerificationMethod thisMethod
     )
     {
-        if ( forgottenPasswordBean.getRecoveryFlags().getRequiredAuthMethods().contains( thisMethod )  )
+        if ( forgottenPasswordBean.getRecoveryFlags().getRequiredAuthMethods().contains( thisMethod ) )
         {
             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> satisfiedMethods = forgottenPasswordBean.getProgress().getSatisfiedMethods();
             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();
-            if ( responseInfoBean != null && responseInfoBean.getHelpdeskCrMap() != null )
+            if (  responseInfoBean.getHelpdeskCrMap() != null )
             {
                 final List<DisplayElement> responseDisplay = new ArrayList<>();
                 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.
  */
 
-package password.pwm.http.servlet;
+package password.pwm.http.servlet.setupresponses;
 
 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.ChallengeSet;
-import com.novell.ldapchai.exception.ChaiError;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 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.PwmDomain;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.ChallengeProfile;
+import password.pwm.config.profile.SetupResponsesProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
@@ -50,6 +44,8 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 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.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
@@ -67,11 +63,9 @@ import password.pwm.ws.server.RestResultBean;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
-import java.io.Serializable;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
@@ -89,10 +83,11 @@ import java.util.Map;
 )
 public class SetupResponsesServlet extends ControlledPwmServlet
 {
-
     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 ),
         setResponses( HttpMethod.POST ),
@@ -122,19 +117,39 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         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
-    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 PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -150,17 +165,11 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             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 );
         }
 
-        // 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.
         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() );
         }
 
+        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
+        if ( !setupResponsesBean.isInitialized() )
+        {
+            initializeBean( pwmRequest, setupResponsesBean );
+        }
+
         // check to see if the user has any challenges assigned
         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 ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_NO_CHALLENGES, errorMsg );
@@ -184,7 +199,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     }
 
     @ActionHandler( action = "confirmResponses" )
-    private ProcessStatus processConfirmResponses( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    private ProcessStatus processConfirmResponses( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
         final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         setupResponsesBean.setConfirmed( true );
@@ -192,7 +208,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     }
 
     @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 PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -246,7 +263,7 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     {
         LOGGER.trace( pwmRequest, () -> "request for skip received" );
 
-        final boolean allowSkip = checkIfAllowSkipCr( pwmRequest );
+        final boolean allowSkip = SetupResponsesUtil.checkIfAllowSkipCr( pwmRequest );
 
         if ( allowSkip )
         {
@@ -262,32 +279,31 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     private ProcessStatus restValidateResponses(
             final PwmRequest pwmRequest
     )
-            throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
+            throws IOException, PwmUnrecoverableException
     {
-        final SetupResponsesBean setupResponsesBean = getSetupResponseBean( pwmRequest );
         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
         {
             // read in the responses from the request
             final Map<Challenge, String> responseMap = readResponsesFromJsonRequest( pwmRequest, setupData );
             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 )
         {
-            success = false;
-            userMessage = e.getErrorInformation().toUserStr( pwmSession, pwmDomain.getConfig() );
+            userMessage = e.getErrorInformation().toUserStr( pwmRequest.getPwmSession(), pwmRequest.getAppConfig() );
         }
 
         final ValidationResponseBean validationResponseBean = new ValidationResponseBean( userMessage, success );
@@ -301,17 +317,17 @@ public class SetupResponsesServlet extends ControlledPwmServlet
 
     @ActionHandler( action = "setHelpdeskResponses" )
     private ProcessStatus processSetHelpdeskResponses( final PwmRequest pwmRequest )
-            throws ChaiUnavailableException, PwmUnrecoverableException, ServletException, IOException
+            throws PwmUnrecoverableException
     {
-        setupResponses( pwmRequest, true );
+        setupResponses( pwmRequest, ResponseMode.helpdesk );
         return ProcessStatus.Continue;
     }
 
     @ActionHandler( action = "setResponses" )
     private ProcessStatus processSetResponses( final PwmRequest pwmRequest )
-            throws ChaiUnavailableException, PwmUnrecoverableException, ServletException, IOException
+            throws PwmUnrecoverableException
     {
-        setupResponses( pwmRequest, false );
+        setupResponses( pwmRequest, ResponseMode.user );
         return ProcessStatus.Continue;
     }
 
@@ -321,45 +337,34 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     {
         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() )
         {
-            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;
         }
 
-        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;
             }
         }
 
-        if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.CHALLENGE_SHOW_CONFIRMATION ) )
+        final SetupResponsesProfile setupResponsesProfile = getSetupProfile( pwmRequest );
+        if ( setupResponsesProfile.readSettingAsBoolean( PwmSetting.SETUP_RESPONSES_SHOW_CONFIRMATION ) )
         {
             if ( !setupResponsesBean.isConfirmed() )
             {
-                pwmRequest.forwardToJsp( JspUrl.SETUP_RESPONSES_CONFIRM );
+                forwardToJsp( pwmRequest, JspUrl.SETUP_RESPONSES_CONFIRM );
                 return;
             }
         }
@@ -367,11 +372,11 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         try
         {
             // everything good, so lets save responses.
-            final ResponseInfoBean responses = generateResponseInfoBean(
+            final ResponseInfoBean responses = SetupResponsesUtil.generateResponseInfoBean(
                     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 );
             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(
             final PwmRequest pwmRequest,
-            final boolean helpdeskMode
+            final ResponseMode responseMode
     )
-            throws PwmUnrecoverableException, IOException, ServletException, ChaiUnavailableException
+            throws PwmUnrecoverableException
     {
         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;
         try
         {
@@ -413,22 +430,14 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         }
         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() );
             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 )
@@ -452,7 +461,8 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             throws PwmDataValidationException, PwmUnrecoverableException
     {
         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(
@@ -462,171 +472,10 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             throws PwmDataValidationException, PwmUnrecoverableException, IOException
     {
         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(
             final PwmRequest pwmRequest,
@@ -634,121 +483,35 @@ public class SetupResponsesServlet extends ControlledPwmServlet
     )
             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 )
             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 )
         {

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

@@ -90,7 +90,7 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
             return false;
         }
 
-        if ( cookieBean.getType() == PwmSessionBean.Type.AUTHENTICATED )
+        if ( cookieBean.getBeanType() == PwmSessionBean.BeanType.AUTHENTICATED )
         {
             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 )
             {
@@ -130,32 +130,29 @@ class CryptoCookieBeanImpl implements SessionBeanProvider
     @Override
     public void saveSessionBeans( final PwmRequest pwmRequest )
     {
-        if ( pwmRequest == null || pwmRequest.getPwmResponse().isCommitted() )
+        if ( pwmRequest == null || pwmRequest.getPwmResponse().isCommitted() || pwmRequest.getPwmResponse() == null  )
         {
             return;
         }
         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
     {
         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

+ 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.PeopleSearchProfile;
 import password.pwm.config.profile.ProfileDefinition;
-import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthService;
 import password.pwm.health.HealthStatus;
@@ -42,6 +41,7 @@ import password.pwm.svc.PwmService;
 import password.pwm.util.java.StringUtil;
 
 import java.util.Collections;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -61,10 +61,10 @@ public enum PwmIfTest
     showHeaderMenu( new ShowHeaderMenuTest() ),
     showVersionHeader( new BooleanAppPropertyTest( AppProperty.HTTP_HEADER_SEND_XVERSION ) ),
     permission( new BooleanPermissionTest() ),
-    otpSetupEnabled( new SetupOTPEnabled() ),
+    otpSetupEnabled( new ProfileEnabled( ProfileDefinition.SetupOTPProfile ) ),
     hasStoredOtpTimestamp( new HasStoredOtpTimestamp() ),
     hasCustomJavascript( new HasCustomJavascript() ),
-    setupChallengeEnabled( new BooleanPwmSettingTest( PwmSetting.CHALLENGE_ENABLE ) ),
+    setupResponsesEnabled( new ProfileEnabled( ProfileDefinition.SetupResponsesProfile ) ),
     shortcutsEnabled( new BooleanPwmSettingTest( PwmSetting.SHORTCUT_ENABLE ) ),
     peopleSearchAvailable( new BooleanPwmSettingTest( PwmSetting.PEOPLE_SEARCH_ENABLE ), new ActorHasProfileTest( ProfileDefinition.PeopleSearch ) ),
     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
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
@@ -526,8 +533,21 @@ public enum PwmIfTest
                 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_CommunicationError,
     Display_ConfirmResponses,
+    Display_ConfirmHelpdeskResponses,
     Display_Day,
     Display_Days,
     Display_ErrorBody,
@@ -268,6 +269,7 @@ public enum Display implements PwmDisplayBundle
     Long_Title_UserEventHistory,
     Long_Title_UserInformation,
     Title_AnsweredQuestions,
+    Title_AnsweredHelpdeskQuestions,
     Title_ActivateUser,
     Title_Admin,
     Title_Application,

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

@@ -696,11 +696,11 @@ public class LdapOperationsHelper
         );
         configBuilder.setSetting(
                 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(
                 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,
                     sessionLabel,
                     getUserIdentity(),
-                    selfCachedReference.getChallengeProfile().getChallengeSet(),
+                    selfCachedReference.getChallengeProfile().getChallengeSet().orElse( null ),
                     selfCachedReference.getResponseInfoBean() );
         }
         catch ( final ChaiUnavailableException e )
@@ -442,7 +442,7 @@ public class UserInfoReader implements UserInfo
             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" );
             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.option.DataStorageMethod;
 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.SetupResponsesProfile;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
@@ -333,7 +336,6 @@ public class CrService extends AbstractPwmService implements PwmService
                 }
             }
         }
-
     }
 
     private void checkResponsesAgainstWordlist( final Map<Challenge, String> responseMap )
@@ -452,7 +454,7 @@ public class CrService extends AbstractPwmService implements PwmService
             final UserIdentity userIdentity,
             final ChaiUser theUser
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
         final DomainConfig config = pwmDomain.getConfig();
 
@@ -504,7 +506,6 @@ public class CrService extends AbstractPwmService implements PwmService
     )
             throws PwmOperationalException, ChaiUnavailableException, ChaiValidationException
     {
-
         int attempts = 0;
         int successes = 0;
         final Map<DataStorageMethod, String> errorMessages = new LinkedHashMap<>();
@@ -540,6 +541,14 @@ public class CrService extends AbstractPwmService implements PwmService
             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 )
         {
             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(
             final PwmDomain pwmDomain,
-            final SessionLabel pwmSession,
+            final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
             final ChallengeSet challengeSet,
             final ResponseInfoBean responseInfoBean
     )
             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();
 
-        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;
         }
 
-        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
         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;
         }
 
@@ -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 ) );
             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." );
                 return true;
             }
@@ -665,13 +684,12 @@ public class CrService extends AbstractPwmService implements PwmService
 
             // check if responses meet the challenge set policy for the user
             //usersResponses.meetsChallengeSetRequirements(challengeSet);
-
-            LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " has good responses" );
+            LOGGER.debug( sessionLabel, () -> "checkIfResponseConfigNeeded: " + userIdentity + " has good responses" );
             return false;
         }
         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;
         }
     }

+ 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.StringUtil;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.HmacAlgorithm;
 import password.pwm.util.secure.PwmBlockAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmRandom;
@@ -62,6 +63,7 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
     protected PwmSecurityKey pwmSecurityKey;
     private PwmBlockAlgorithm defaultBlockAlgorithm;
     private PwmHashAlgorithm defaultHashAlgorithm;
+    private HmacAlgorithm defaultHmacAlgorithm;
     private PwmRandom pwmRandom;
 
     private final StatisticCounterBundle<StatKey> stats = new StatisticCounterBundle<>( StatKey.class );
@@ -70,6 +72,8 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
     {
         hashOperations,
         hashBytes,
+        hmacOperations,
+        hmacBytes,
         encryptOperations,
         encryptBytes,
         decryptOperations,
@@ -101,6 +105,10 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
             final String defaultHashAlgString = pwmApplication.getConfig().readAppProperty( AppProperty.SECURITY_DEFAULT_EPHEMERAL_HASH_ALG );
             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() ) );
 
         return STATUS.OPEN;
@@ -226,6 +234,16 @@ public abstract class AbstractSecureService extends AbstractPwmService implement
         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
     public String hash(
             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() )
             {

+ 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.config.profile.ChallengeProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.JsonUtil;
@@ -75,7 +77,8 @@ public class ImportResponsesCommand extends AbstractCliCommand
                     {
                         final ChallengeProfile challengeProfile = pwmDomain.getCrService().readUserChallengeProfile(
                                 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 ResponseInfoBean responseInfoBean = inputData.toResponseInfoBean( PwmConstants.DEFAULT_LOCALE, challengeSet.getIdentifier() );
                         pwmDomain.getCrService().writeResponses( null, userIdentity, user, userGuid, responseInfoBean );

+ 4 - 3
server/src/main/java/password/pwm/util/cli/commands/TokenInfoCommand.java

@@ -22,6 +22,7 @@ package password.pwm.util.cli.commands;
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
@@ -41,9 +42,9 @@ public class TokenInfoCommand extends AbstractCliCommand
             throws Exception
     {
         final String tokenKey = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTION_TOKEN );
-        final String tokenId = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTION_DOMAIN );
+        final String domainId = ( String ) cliEnvironment.getOptions().get( TOKEN_KEY_OPTION_DOMAIN );
         final PwmApplication pwmApplication = cliEnvironment.getPwmApplication();
-        final PwmDomain pwmDomain = pwmApplication.domains().get( tokenId );
+        final PwmDomain pwmDomain = pwmApplication.domains().get( DomainID.create( domainId ) );
 
         final TokenService tokenService = pwmDomain.getTokenService();
         TokenPayload tokenPayload = null;
@@ -60,7 +61,7 @@ public class TokenInfoCommand extends AbstractCliCommand
         out( " token: " + tokenKey );
         if ( lookupError != null )
         {
-            out( "result: error during token lookup: " + lookupError.toString() );
+            out( "result: error during token lookup: " + lookupError );
         }
         else if ( tokenPayload == null )
         {

+ 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 ) ) );
     }
 
+    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(
             final HmacAlgorithm hmacAlgorithm,
             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.cr.ChaiChallenge;
 import com.novell.ldapchai.cr.Challenge;
-import com.novell.ldapchai.cr.ChallengeSet;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.bean.ChallengeBean;
 import com.novell.ldapchai.exception.ChaiException;
@@ -44,9 +43,9 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.svc.cr.CrService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
-import password.pwm.svc.cr.CrService;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.ws.server.RestMethodHandler;
 import password.pwm.ws.server.RestRequest;
@@ -173,8 +172,6 @@ public class RestChallengesServer extends RestServlet
 
             // gather data
             final ResponseSet responseSet;
-            final ChallengeSet challengeSet;
-            final ChallengeSet helpdeskChallengeSet;
             final String outputUsername;
 
             final ChaiUser chaiUser = targetUserIdentity.getChaiUser();
@@ -195,8 +192,6 @@ public class RestChallengesServer extends RestServlet
                     userLocale
             );
 
-            challengeSet = challengeProfile.getChallengeSet();
-            helpdeskChallengeSet = challengeProfile.getHelpdeskChallengeSet();
             outputUsername = targetUserIdentity.getUserIdentity().toDelimitedKey();
 
             // build output
@@ -213,15 +208,18 @@ public class RestChallengesServer extends RestServlet
                     jsonData.minimumRandoms = responseSet.getChallengeSet().getMinRandomRequired();
                 }
                 final Policy policy = new Policy();
-                if ( challengeSet != null )
+
+                challengeProfile.getChallengeSet().ifPresent( challengeSet ->
                 {
                     policy.challenges = challengesToBeans( challengeSet.getChallenges() );
                     policy.minimumRandoms = challengeSet.getMinRandomRequired();
-                }
-                if ( helpdeskChallengeSet != null && helpdesk )
+                } );
+
+                challengeProfile.getHelpdeskChallengeSet().ifPresent( helpdeskChallengeSet ->
                 {
                     policy.helpdeskChallenges = challengesToBeans( helpdeskChallengeSet.getChallenges() );
-                }
+                } );
+
                 if ( policy.challenges != null || policy.helpdeskChallenges != null )
                 {
                     jsonData.policy = policy;
@@ -279,7 +277,9 @@ public class RestChallengesServer extends RestServlet
                     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 );
             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.defaultEphemeralBlockAlg=AES128_GCM
 security.defaultEphemeralHashAlg=SHA512
+security.defaultEphemeralHmacAlg=HmacSHA512
 security.config.minSecurityKeyLength=32
 seedlist.builtin.path=/WEB-INF/seedlist.zip
 smtp.io.connectTimeoutMs=10000

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

@@ -2120,11 +2120,25 @@
             <value>true</value>
         </default>
     </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">
         <default>
             <value>true</value>
         </default>
     </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">
         <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>
@@ -4243,7 +4257,12 @@
     </category>
     <category hidden="false" scope="DOMAIN" key="PASSWORD_GLOBAL">
     </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 hidden="false" scope="DOMAIN" key="CHALLENGE_POLICY">
         <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_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_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_Days=days
 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_VerificationSend=Before this user can be selected, the user's identity must be verified.  Please select a verification method.
 Title_AnsweredQuestions=Answered Questions
+Title_AnsweredHelpdeskQuestions=Answered Helpdesk Questions
 Title_ActivateUser=Activate Account
 Title_Admin=Administration
 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_CAS_SSO=
 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_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.
@@ -149,7 +151,9 @@ Category_Label_BASIC_SSO=Basic Authentication
 Category_Label_CAPTCHA=Captcha
 Category_Label_CAS_SSO=CAS SSO
 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_SETTINGS=Settings
 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.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.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.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.
@@ -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.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_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.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.
@@ -821,6 +827,7 @@ Setting_Label_challenge.caseInsensitive=Case Insensitive Responses
 Setting_Label_challenge.enable=Enable Setup Responses
 Setting_Label_challenge.enforceMinimumPasswordLifetime=Enforce Minimum Password Lifetime
 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.randomChallenges=Help Desk Random 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.forceSetup=Force Setup of One Time Passwords
 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.encrypt=Encrypt OTP secret
 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.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() );
     }
 

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

@@ -458,12 +458,13 @@
             <% } %>
         </table>
         <br/>
+        <% { %><%-- Begin Challenge Profile --%>
         <table>
             <tr>
                 <td colspan="10" class="title">Challenge Profile</td>
             </tr>
             <% final ChallengeProfile challengeProfile = userDebugDataBean.getUserInfo().getChallengeProfile(); %>
-            <% if (challengeProfile == null) { %>
+            <% if ( challengeProfile == null ) { %>
             <tr>
                 <td>Assigned Profile</td>
                 <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
@@ -494,7 +495,8 @@
                             <td class="key">Enforce Wordlist</td>
                             <td class="key">Max Question Characters</td>
                         </tr>
-                        <% for (final Challenge challenge : challengeProfile.getChallengeSet().getChallenges()) { %>
+                        <% if ( challengeProfile.hasChallenges() ) { %>
+                        <% for (final Challenge challenge : challengeProfile.getChallengeSet().get().getChallenges()) { %>
                         <tr>
                             <td>
                                 <%= challenge.isAdminDefined() ? "Admin Defined" : "User Defined" %>
@@ -519,13 +521,58 @@
                             </td>
                         </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>
                 </td>
             </tr>
             <% } %>
         </table>
-
+        <% } %><%-- End Challenge Profile --%>
         <% } %>
+        </tr>
         <div class="buttonbar">
             <form method="get" class="pwm-form">
                 <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="java.util.ArrayList" %>
 <%@ 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" %>
 <%@ 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 ----------------------------------------------%>
-<% if (!setupData.getChallengeSet().getRequiredChallenges().isEmpty()) { %>
+<% if (!challengeSet.getRequiredChallenges().isEmpty()) { %>
 <p><pwm:display key="Display_SetupRequiredResponses"/></p>
 <%
     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++) { %>
 <h2>
     <select name="PwmResponse_Q_Random_<%=index%>" id="PwmResponse_Q_Random_<%=index%>" style="width:70%" <pwm:autofocus/> class="simpleModeResponseSelection"
@@ -101,8 +105,8 @@
 </pwm:script>
 <% } else { %>
 <%---------------------- 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()) {
         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="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.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>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
@@ -41,34 +45,35 @@
     </jsp:include>
     <div id="centerbody">
         <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" %>
+        <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.user ) ) { %>
         <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>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
-            <%= StringUtil.escapeHtml(responseText) %>
+            <%= StringUtil.escapeHtml(entry.getValue()) %>
         </p>
         <% } %>
+        <% } %>
+        <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.helpdesk ) ) { %>
         <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>
             <span class="pwm-icon pwm-icon-chevron-circle-right"></span>
-            <%= StringUtil.escapeHtml(responseText) %>
+            <%= StringUtil.escapeHtml(entry.getValue()) %>
         </p>
         <% } %>
+        <% } %>
         <br/>
         <div class="buttonbar">
             <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">
         <h1 id="page-content-title"><pwm:display key="Title_ConfirmResponses" displayIfMissing="true"/></h1>
         <p>
-            <% if (responseInfoBean != null && responseInfoBean.getTimestamp() != null) { %>
+            <% if (responseInfoBean.getTimestamp() != null) { %>
             <pwm:display key="Display_WarnExistingResponseTime" value1="@ResponseSetupTime@"/>
             <% } else { %>
             <pwm:display key="Display_WarnExistingResponse"/>
@@ -50,9 +50,17 @@
         </p>
         <%@ include file="fragment/message.jsp" %>
         <br/>
+        <% if (responseInfoBean.getCrMap() != null && !responseInfoBean.getCrMap().isEmpty() ) { %>
         <h2><pwm:display key="Title_AnsweredQuestions"/></h2>
         <% 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/>
         <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.tag.conditional.PwmIfTest" %>
 <%@ 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>
 
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
 <%@ 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%>"/>">
 <%@ include file="fragment/header.jsp" %>
 <body>
@@ -47,7 +50,6 @@
               enctype="application/x-www-form-urlencoded" id="form-setupResponses" class="pwm-form" autocomplete="off">
             <%@ include file="fragment/message.jsp" %>
             <div id="pwm-setupResponsesDiv">
-                <% request.setAttribute("setupData",responseBean.getHelpdeskResponseData()); %>
                 <jsp:include page="fragment/setupresponses-form.jsp"/>
             </div>
             <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:display key="Button_SetResponses"/>
                 </button>
+                <% if ( SetupResponsesUtil.hasChallenges( JspUtility.getPwmRequest( pageContext ), ResponseMode.user ) ) { %>
                 <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:display key="Button_GoBack"/>
                 </button>
+                <% } %>
                 <input type="hidden" id="pwmFormID" name="pwmFormID" value="<pwm:FormID/>"/>
             </div>
         </form>
@@ -72,7 +76,7 @@
 </div>
 <pwm:script>
     <script type="text/javascript">
-        PWM_GLOBAL['responseMode'] = "helpdesk";
+        PWM_GLOBAL['responseMode'] = "<%=ResponseMode.helpdesk%>";
         PWM_GLOBAL['startupFunctions'].push(function(){
             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.PwmRequestAttribute" %>
+<%@ page import="password.pwm.http.servlet.setupresponses.ResponseMode" %>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
-<% final SetupResponsesBean responseBean = (SetupResponsesBean)JspUtility.getAttribute(pageContext, PwmRequestAttribute.ModuleBean); %>
 <% final boolean allowSkip = JspUtility.getBooleanAttribute( pageContext, PwmRequestAttribute.SetupResponses_AllowSkip ); %>
 <html lang="<pwm:value name="<%=PwmValue.localeCode%>"/>" dir="<pwm:value name="<%=PwmValue.localeDir%>"/>">
 <%@ 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">
             <%@ include file="fragment/message.jsp" %>
             <div id="pwm-setupResponsesDiv">
-            <% request.setAttribute("setupData",responseBean.getResponseData()); %>
             <jsp:include page="fragment/setupresponses-form.jsp"/>
             </div>
             <div class="buttonbar">
@@ -71,7 +70,7 @@
 </form>
 <pwm:script>
     <script type="text/javascript">
-        PWM_GLOBAL['responseMode'] = "user";
+        PWM_GLOBAL['responseMode'] = "<%=ResponseMode.user%>";
         PWM_GLOBAL['startupFunctions'].push(function(){
             PWM_RESPONSES.startupResponsesPage();
         });

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

@@ -82,18 +82,16 @@
                 </a>
             </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>
-                    </a>
-                </pwm:if>
+                    </div>
+                </a>
             </pwm:if>
 
             <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');
                     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(){