Browse Source

further domain refactoring #2

Jason Rivard 4 năm trước cách đây
mục cha
commit
0e85726d54
100 tập tin đã thay đổi với 1711 bổ sung1345 xóa
  1. 2 8
      build/checkstyle-import.xml
  2. 1 1
      client/pom.xml
  3. 2 2
      data-service/pom.xml
  4. 1 1
      docker/pom.xml
  5. 1 1
      onejar/pom.xml
  6. 12 12
      pom.xml
  7. 4 33
      server/pom.xml
  8. 6 0
      server/src/main/java/password/pwm/AppProperty.java
  9. 2 2
      server/src/main/java/password/pwm/PwmAboutProperty.java
  10. 106 75
      server/src/main/java/password/pwm/PwmApplication.java
  11. 1 1
      server/src/main/java/password/pwm/PwmConstants.java
  12. 19 17
      server/src/main/java/password/pwm/PwmDomain.java
  13. 0 1
      server/src/main/java/password/pwm/bean/DomainID.java
  14. 13 8
      server/src/main/java/password/pwm/bean/SessionLabel.java
  15. 9 9
      server/src/main/java/password/pwm/bean/UserIdentity.java
  16. 7 40
      server/src/main/java/password/pwm/config/AppConfig.java
  17. 0 86
      server/src/main/java/password/pwm/config/ConfigurationUtil.java
  18. 56 60
      server/src/main/java/password/pwm/config/DomainConfig.java
  19. 41 29
      server/src/main/java/password/pwm/config/PwmSetting.java
  20. 6 4
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  21. 5 0
      server/src/main/java/password/pwm/config/PwmSettingFlag.java
  22. 5 0
      server/src/main/java/password/pwm/config/PwmSettingMetaDataReader.java
  23. 53 0
      server/src/main/java/password/pwm/config/StoredSettingReader.java
  24. 18 6
      server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  25. 20 11
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  26. 13 5
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  27. 1 1
      server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  28. 2 2
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  29. 3 2
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  30. 4 2
      server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java
  31. 11 6
      server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java
  32. 17 5
      server/src/main/java/password/pwm/config/value/AbstractValue.java
  33. 2 1
      server/src/main/java/password/pwm/config/value/FileValue.java
  34. 4 20
      server/src/main/java/password/pwm/config/value/ValueTypeConverter.java
  35. 11 10
      server/src/main/java/password/pwm/error/PwmError.java
  36. 15 12
      server/src/main/java/password/pwm/health/ApplianceStatusChecker.java
  37. 3 8
      server/src/main/java/password/pwm/health/CertificateChecker.java
  38. 243 118
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  39. 4 3
      server/src/main/java/password/pwm/health/DatabaseStatusChecker.java
  40. 5 4
      server/src/main/java/password/pwm/health/HealthRecord.java
  41. 40 44
      server/src/main/java/password/pwm/health/HealthService.java
  42. 10 1
      server/src/main/java/password/pwm/health/HealthSupplier.java
  43. 3 1
      server/src/main/java/password/pwm/health/JavaChecker.java
  44. 69 55
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  45. 2 2
      server/src/main/java/password/pwm/health/LocalDBHealthChecker.java
  46. 3 35
      server/src/main/java/password/pwm/http/HttpEventManager.java
  47. 1 0
      server/src/main/java/password/pwm/http/HttpHeader.java
  48. 3 7
      server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java
  49. 14 22
      server/src/main/java/password/pwm/http/PwmCookiePath.java
  50. 3 3
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  51. 8 9
      server/src/main/java/password/pwm/http/PwmRequest.java
  52. 4 8
      server/src/main/java/password/pwm/http/PwmSession.java
  53. 2 2
      server/src/main/java/password/pwm/http/auth/BasicFilterAuthenticationProvider.java
  54. 2 0
      server/src/main/java/password/pwm/http/bean/DisplayElement.java
  55. 1 1
      server/src/main/java/password/pwm/http/bean/SetupOtpBean.java
  56. 213 0
      server/src/main/java/password/pwm/http/filter/DomainInitFilter.java
  57. 2 2
      server/src/main/java/password/pwm/http/filter/ObsoleteUrlFilter.java
  58. 24 82
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  59. 3 5
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  60. 9 24
      server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  61. 5 5
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  62. 1 1
      server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  63. 6 6
      server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java
  64. 11 10
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  65. 6 4
      server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  66. 9 10
      server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java
  67. 9 5
      server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java
  68. 4 4
      server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  69. 11 9
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  70. 6 5
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  71. 10 10
      server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java
  72. 67 36
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  73. 1 1
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  74. 39 26
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  75. 2 1
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java
  76. 58 50
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  77. 21 45
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  78. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/DomainStateReader.java
  79. 18 5
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java
  80. 30 0
      server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeSettings.java
  81. 23 7
      server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java
  82. 16 10
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  83. 3 3
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  84. 4 4
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java
  85. 28 28
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  86. 2 1
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java
  87. 21 19
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java
  88. 10 7
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  89. 61 48
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  90. 10 9
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  91. 8 3
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java
  92. 6 6
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  93. 9 8
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  94. 4 1
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  95. 11 11
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  96. 7 14
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchService.java
  97. 4 4
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  98. 1 1
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  99. 2 2
      server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java
  100. 22 16
      server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java

+ 2 - 8
build/checkstyle-import.xml

@@ -120,22 +120,16 @@
     </subpackage>
 
     <!-- nmas -->
-    <subpackage name="util.operations.cr">
+    <subpackage name="svc.cr">
         <allow pkg="com.novell.ldap"/>
         <allow pkg="com.novell.security.nmas"/>
     </subpackage>
 
     <!-- database -->
-    <subpackage name="util.db">
+    <subpackage name="svc.db">
         <allow pkg="java.sql"/>
     </subpackage>
 
-    <subpackage name="util.java">
-        <allow class="net.iharder.Base64"/>
-        <allow pkg="org.apache.commons.codec"/>
-        <allow pkg="org.apache.commons.lang3"/>
-    </subpackage>
-
     <subpackage name="util.localdb">
         <allow pkg="java.sql"/>
     </subpackage>

+ 1 - 1
client/pom.xml

@@ -17,7 +17,7 @@
         <npm.version>6.14.8</npm.version>
     </properties>
 
-    <name>PWM Password Self Service: Angular Client JAR</name>
+    <name>PWM Password Self Service: Angular Client WebJAR</name>
 
     <profiles>
         <profile>

+ 2 - 2
data-service/pom.xml

@@ -120,7 +120,7 @@
         <dependency>
             <groupId>commons-net</groupId>
             <artifactId>commons-net</artifactId>
-            <version>3.7.2</version>
+            <version>3.8.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -130,7 +130,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
-            <version>3.11</version>
+            <version>3.12.0</version>
         </dependency>
         <dependency>
             <groupId>com.sun.mail</groupId>

+ 1 - 1
docker/pom.xml

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

+ 1 - 1
onejar/pom.xml

@@ -16,7 +16,7 @@
     <name>PWM Password Self Service: Executable Server JAR</name>
 
     <properties>
-        <tomcat.version>9.0.39</tomcat.version>
+        <tomcat.version>9.0.44</tomcat.version>
     </properties>
 
     <build>

+ 12 - 12
pom.xml

@@ -43,7 +43,7 @@
         <skipTests>false</skipTests>
 
         <!-- git.commit.time is populated via git-commit-id-plugin and results in a (hopefully) reproducible maven build -->
-        <project.build.outputTimestamp>${git.commit.time}</project.build.outputTimestamp>
+        <project.build.outreportservreportservputTimestamp>${git.commit.time}</project.build.outreportservreportservputTimestamp>
     </properties>
 
     <modules>
@@ -232,12 +232,12 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-checkstyle-plugin</artifactId>
-                <version>3.1.1</version>
+                <version>3.1.2</version>
                 <dependencies>
                     <dependency>
                         <groupId>com.puppycrawl.tools</groupId>
                         <artifactId>checkstyle</artifactId>
-                        <version>8.39</version>
+                        <version>8.41</version>
                     </dependency>
                 </dependencies>
                 <executions>
@@ -321,7 +321,7 @@
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
                         <artifactId>spotbugs</artifactId>
-                        <version>4.2.0</version>
+                        <version>4.2.2</version>
                     </dependency>
                 </dependencies>
                 <configuration>
@@ -365,7 +365,7 @@
             <plugin>
                 <groupId>org.owasp</groupId>
                 <artifactId>dependency-check-maven</artifactId>
-                <version>6.0.5</version>
+                <version>6.1.2</version>
                 <executions>
                     <execution>
                         <goals>
@@ -382,13 +382,13 @@
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <version>1.18.16</version>
+            <version>1.18.20</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
-            <version>4.2.0</version>
+            <version>4.2.2</version>
             <scope>provided</scope>
         </dependency>
 
@@ -396,19 +396,19 @@
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
-            <version>4.13.1</version>
+            <version>4.13.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>3.7.0</version>
+            <version>3.8.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
-            <version>3.18.1</version>
+            <version>3.19.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -426,13 +426,13 @@
         <dependency>
             <groupId>org.openjdk.jmh</groupId>
             <artifactId>jmh-core</artifactId>
-            <version>1.27</version>
+            <version>1.28</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.openjdk.jmh</groupId>
             <artifactId>jmh-generator-annprocess</artifactId>
-            <version>1.27</version>
+            <version>1.28</version>
             <scope>test</scope>
         </dependency>
     </dependencies>

+ 4 - 33
server/pom.xml

@@ -38,35 +38,6 @@
     </profiles>
     <build>
         <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.8.1</version>
-                <configuration>
-                    <source>${maven.compiler.source}</source>
-                    <target>${maven.compiler.target}</target>
-                    <release>${maven.compiler.release}</release>
-                    <showWarnings>true</showWarnings>
-                    <compilerArgs>
-                        <arg>-Xmaxwarns</arg>
-                        <arg>9999</arg>
-                        <arg>-XDcompilePolicy=simple</arg>
-                        <arg>-Xplugin:ErrorProne</arg>
-                    </compilerArgs>
-                    <annotationProcessorPaths>
-                        <path>
-                            <groupId>com.google.errorprone</groupId>
-                            <artifactId>error_prone_core</artifactId>
-                            <version>2.4.0</version>
-                        </path>
-                        <path>
-                            <groupId>org.projectlombok</groupId>
-                            <artifactId>lombok</artifactId>
-                            <version>1.18.16</version>
-                        </path>
-                    </annotationProcessorPaths>
-                </configuration>
-            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
@@ -232,7 +203,7 @@
         <dependency>
             <groupId>commons-net</groupId>
             <artifactId>commons-net</artifactId>
-            <version>3.7.2</version>
+            <version>3.8.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -252,7 +223,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
-            <version>3.11</version>
+            <version>3.12.0</version>
         </dependency>
         <dependency>
             <groupId>commons-validator</groupId>
@@ -338,12 +309,12 @@
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
-            <version>2.8.8</version>
+            <version>3.0.0</version>
         </dependency>
         <dependency>
             <groupId>com.nulab-inc</groupId>
             <artifactId>zxcvbn</artifactId>
-            <version>1.3.1</version>
+            <version>1.4.0</version>
         </dependency>
         <dependency>
             <groupId>com.github.ziplet</groupId>

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

@@ -131,6 +131,7 @@ public enum AppProperty
     HTTP_CLIENT_CONNECT_TIMEOUT_MS                  ( "http.client.connectTimeoutMs" ),
     HTTP_CLIENT_REQUEST_TIMEOUT_MS                  ( "http.client.requestTimeoutMs" ),
     HTTP_CLIENT_RESPONSE_MAX_SIZE                   ( "http.client.response.maxSize" ),
+    HTTP_CLIENT_IMPLEMENTATION                      ( "http.client.implementation" ),
     HTTP_CLIENT_ENABLE_HOSTNAME_VERIFICATION        ( "http.client.enableHostnameVerification" ),
     HTTP_CLIENT_PROMISCUOUS_WORDLIST_ENABLE         ( "http.client.promiscuous.wordlist.enable" ),
     HTTP_ENABLE_GZIP                                ( "http.gzip.enable" ),
@@ -138,6 +139,7 @@ public enum AppProperty
     HTTP_HEADER_SERVER                              ( "http.header.server" ),
     HTTP_HEADER_SEND_CONTENT_LANGUAGE               ( "http.header.sendContentLanguage" ),
     HTTP_HEADER_SEND_XAMB                           ( "http.header.sendXAmb" ),
+    HTTP_HEADER_SEND_XDOMAIN                        ( "http.header.sendXDomain" ),
     HTTP_HEADER_SEND_XINSTANCE                      ( "http.header.sendXInstance" ),
     HTTP_HEADER_SEND_XNOISE                         ( "http.header.sendXNoise" ),
     HTTP_HEADER_SEND_XSESSIONID                     ( "http.header.sendXSessionID" ),
@@ -145,6 +147,7 @@ public enum AppProperty
     HTTP_HEADER_SEND_XCONTENTTYPEOPTIONS            ( "http.header.sendXContentTypeOptions" ),
     HTTP_HEADER_SEND_XXSSPROTECTION                 ( "http.header.sendXXSSProtection" ),
     HTTP_HEADER_NOISE_LENGTH                        ( "http.header.noise.length" ),
+    HTTP_HEADER_CACHE_CONTROL                       ( "http.header.cacheControl" ),
     HTTP_HEADER_CSP_NONCE_BYTES                     ( "http.header.csp.nonce.bytes" ),
     HTTP_PARAM_NAME_FORWARD_URL                     ( "http.parameter.forward" ),
     HTTP_PARAM_NAME_LOGOUT_URL                      ( "http.parameter.logout" ),
@@ -191,6 +194,7 @@ public enum AppProperty
     INTRUDER_MAX_DELAY_PENALTY_MS                   ( "intruder.maximumDelayPenaltyMS" ),
     INTRUDER_DELAY_PER_COUNT_MS                     ( "intruder.delayPerCountMS" ),
     INTRUDER_DELAY_MAX_JITTER_MS                    ( "intruder.delayMaxJitterMS" ),
+    INTRUDER_STORAGE_HASH_ALGORITHM                 ( "intruder.storageHashAlgorithm" ),
     HEALTHCHECK_ENABLED                             ( "healthCheck.enabled" ),
     HEALTHCHECK_NOMINAL_CHECK_INTERVAL              ( "healthCheck.nominalCheckIntervalSeconds" ),
     HEALTHCHECK_MIN_CHECK_INTERVAL                  ( "healthCheck.minimumCheckIntervalSeconds" ),
@@ -373,6 +377,8 @@ public enum AppProperty
     WORDLIST_BUILTIN_PATH                           ( "wordlist.builtin.path" ),
     WORDLIST_CHAR_LENGTH_MAX                        ( "wordlist.maxCharLength" ),
     WORDLIST_CHAR_LENGTH_MIN                        ( "wordlist.minCharLength" ),
+    WORDLIST_BUCKET_CHECK_WARNING_TIMEOUT_MS        ( "wordlist.bucketCheckLogWarningTimeoutMs" ),
+    WORDLIST_WARMUP_COUNT                           ( "wordlist.warmup.count" ),
     WORDLIST_IMPORT_AUTO_IMPORT_RECHECK_SECONDS     ( "wordlist.import.autoImportRecheckSeconds" ),
     WORDLIST_IMPORT_DURATION_GOAL_MS                ( "wordlist.import.durationGoalMS" ),
     WORDLIST_IMPORT_MIN_FREE_SPACE                  ( "wordlist.import.minFreeSpace" ),

+ 2 - 2
server/src/main/java/password/pwm/PwmAboutProperty.java

@@ -23,7 +23,7 @@ package password.pwm;
 import password.pwm.config.PwmSetting;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.LdapConnectionService;
-import password.pwm.util.db.DatabaseService;
+import password.pwm.svc.db.DatabaseService;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
@@ -63,7 +63,7 @@ public enum PwmAboutProperty
     app_emailQueueOldestTime( null, pwmApplication -> format( pwmApplication.getEmailQueue().eldestItem() ) ),
     app_smsQueueSize( null, pwmApplication -> Integer.toString( pwmApplication.getSmsQueue().queueSize() ) ),
     app_smsQueueOldestTime( null, pwmApplication -> format( pwmApplication.getSmsQueue().eldestItem() ) ),
-    app_syslogQueueSize( null, pwmApplication -> Integer.toString( pwmApplication.getAuditManager().syslogQueueSize() ) ),
+    app_syslogQueueSize( null, pwmApplication -> Integer.toString( pwmApplication.getAuditService().syslogQueueSize() ) ),
     app_localDbLogSize( null, pwmApplication -> Integer.toString( pwmApplication.getLocalDBLogger().getStoredEventCount() ) ),
     app_localDbLogOldestTime( null, pwmApplication -> format( pwmApplication.getLocalDBLogger().getTailDate() ) ),
     app_localDbStorageSize( null, pwmApplication -> StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( pwmApplication.getLocalDB().getFileLocation() ) ) ),

+ 106 - 75
server/src/main/java/password/pwm/PwmApplication.java

@@ -41,23 +41,25 @@ import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmServiceEnum;
 import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.cache.CacheService;
+import password.pwm.svc.db.DatabaseAccessor;
+import password.pwm.svc.db.DatabaseService;
 import password.pwm.svc.email.EmailService;
 import password.pwm.svc.event.AuditEvent;
-import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.AuditService;
-import password.pwm.svc.event.SystemAuditRecord;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.httpclient.HttpClientService;
-import password.pwm.svc.intruder.IntruderService;
 import password.pwm.svc.intruder.IntruderRecordType;
+import password.pwm.svc.intruder.IntruderSystemService;
 import password.pwm.svc.node.NodeService;
-import password.pwm.svc.pwnotify.PwNotifyService;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.secure.SystemSecureService;
 import password.pwm.svc.sessiontrack.SessionTrackService;
 import password.pwm.svc.sessiontrack.UserAgentUtils;
 import password.pwm.svc.shorturl.UrlShortenerService;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.svc.stats.StatisticsService;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.wordlist.SeedlistService;
 import password.pwm.svc.wordlist.SharedHistoryService;
@@ -66,8 +68,6 @@ import password.pwm.util.MBeanUtility;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.cli.commands.ExportHttpsTomcatConfigCommand;
-import password.pwm.util.db.DatabaseAccessor;
-import password.pwm.util.db.DatabaseService;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
@@ -81,7 +81,6 @@ import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogManager;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
-import password.pwm.util.queue.SmsQueueManager;
 import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.util.secure.PwmRandom;
 
@@ -96,11 +95,13 @@ import java.security.KeyStore;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -118,7 +119,9 @@ public class PwmApplication
     private Map<DomainID, PwmDomain> domains;
     private String runtimeNonce = PwmRandom.getInstance().randomUUID().toString();
 
-    private final PwmServiceManager pwmServiceManager = new PwmServiceManager( this, DomainID.systemId(), PwmServiceEnum.forScope( PwmSettingScope.SYSTEM ) );
+    private final PwmServiceManager pwmServiceManager = new PwmServiceManager(
+            SessionLabel.SYSTEM_LABEL,
+            this, DomainID.systemId(), PwmServiceEnum.forScope( PwmSettingScope.SYSTEM ) );
 
     private final Instant startupTime = Instant.now();
     private Instant installTime = Instant.now();
@@ -136,9 +139,10 @@ public class PwmApplication
     {
         this.pwmEnvironment = Objects.requireNonNull( pwmEnvironment );
 
-
-        pwmEnvironment.verifyIfApplicationPathIsSetProperly();
-
+        if ( !pwmEnvironment.isInternalRuntimeInstance() )
+        {
+            pwmEnvironment.verifyIfApplicationPathIsSetProperly();
+        }
 
         try
         {
@@ -249,7 +253,7 @@ public class PwmApplication
         LOGGER.debug( () -> "application environment flags: " + JsonUtil.serializeCollection( pwmEnvironment.getFlags() ) );
         LOGGER.debug( () -> "application environment parameters: " + JsonUtil.serializeMap( pwmEnvironment.getParameters() ) );
 
-        pwmScheduler = new PwmScheduler( getInstanceID() );
+        pwmScheduler = new PwmScheduler( this );
 
         pwmServiceManager.initAllServices();
 
@@ -262,7 +266,7 @@ public class PwmApplication
         {
             final TimeDuration totalTime = TimeDuration.fromCurrent( startTime );
             LOGGER.info( () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")" );
-            StatisticsManager.incrementStat( this, Statistic.PWM_STARTUPS );
+            StatisticsClient.incrementStat( this, Statistic.PWM_STARTUPS );
             LOGGER.debug( () -> "buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE );
 
             pwmScheduler.immediateExecuteRunnableInNewThread( this::postInitTasks, this.getClass().getSimpleName() + " postInit tasks" );
@@ -302,10 +306,6 @@ public class PwmApplication
     {
         final Instant startTime = Instant.now();
 
-        getPwmScheduler().immediateExecuteRunnableInNewThread( UserAgentUtils::initializeCache, "initialize useragent cache" );
-        getPwmScheduler().immediateExecuteRunnableInNewThread( PwmSettingMetaDataReader::initCache, "initialize PwmSetting cache" );
-
-
         if ( Boolean.parseBoolean( getConfig().readAppProperty( AppProperty.LOGGING_OUTPUT_CONFIGURATION ) ) )
         {
             outputConfigurationToLog( this );
@@ -313,18 +313,7 @@ public class PwmApplication
         }
 
         // send system audit event
-        try
-        {
-            final SystemAuditRecord auditRecord = new AuditRecordFactory( this ).createSystemAuditRecord(
-                    AuditEvent.STARTUP,
-                    null
-            );
-            getAuditManager().submit( null, auditRecord );
-        }
-        catch ( final PwmException e )
-        {
-            LOGGER.warn( () -> "unable to submit start alert event " + e.getMessage() );
-        }
+        AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.STARTUP );
 
         try
         {
@@ -338,7 +327,7 @@ public class PwmApplication
 
         try
         {
-            this.getIntruderService().clear( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
+            this.getAdminDomain().getIntruderService().clear( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
         }
         catch ( final Exception e )
         {
@@ -366,11 +355,15 @@ public class PwmApplication
             }
         }
 
+        getPwmScheduler().immediateExecuteRunnableInNewThread( UserAgentUtils::initializeCache, "initialize useragent cache" );
+        getPwmScheduler().immediateExecuteRunnableInNewThread( PwmSettingMetaDataReader::initCache, "initialize PwmSetting cache" );
+
         MBeanUtility.registerMBean( this );
         LOGGER.trace( () -> "completed post init tasks", () -> TimeDuration.fromCurrent( startTime ) );
     }
 
-    public static PwmApplication createPwmApplication( final PwmEnvironment pwmEnvironment ) throws PwmUnrecoverableException
+    public static PwmApplication createPwmApplication( final PwmEnvironment pwmEnvironment )
+            throws PwmUnrecoverableException
     {
         return new PwmApplication( pwmEnvironment );
     }
@@ -427,24 +420,17 @@ public class PwmApplication
 
     public void shutdown( final boolean keepServicesRunning )
     {
-        LOGGER.warn( () -> "shutting down" );
+        final Instant startTime = Instant.now();
+
+        if ( keepServicesRunning )
         {
-            // send system audit event
-            try
-            {
-                final SystemAuditRecord auditRecord = new AuditRecordFactory( this ).createSystemAuditRecord(
-                        AuditEvent.SHUTDOWN,
-                        null
-                );
-                if ( getAuditManager() != null )
-                {
-                    getAuditManager().submit( null, auditRecord );
-                }
-            }
-            catch ( final PwmException e )
-            {
-                LOGGER.warn( () -> "unable to submit shutdown alert event " + e.getMessage() );
-            }
+            LOGGER.warn( () -> "preparing for restart" );
+            AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.RESTART );
+        }
+        else
+        {
+            LOGGER.warn( () -> "shutting down" );
+            AuditServiceClient.submitSystemEvent( this, SessionLabel.SYSTEM_LABEL, AuditEvent.SHUTDOWN );
         }
 
         MBeanUtility.unregisterMBean( this );
@@ -489,8 +475,14 @@ public class PwmApplication
         {
             try
             {
-                LOGGER.trace( () -> "beginning close of LocalDB" );
+                final Instant startCloseDbTime = Instant.now();
+                LOGGER.debug( () -> "beginning close of LocalDB" );
                 localDB.close();
+                final TimeDuration closeLocalDbDuration = TimeDuration.fromCurrent( startCloseDbTime );
+                if ( closeLocalDbDuration.isLongerThan( TimeDuration.SECONDS_10 ) )
+                {
+                    LOGGER.info( () -> "completed close of LocalDB", () -> closeLocalDbDuration );
+                }
             }
             catch ( final Exception e )
             {
@@ -506,7 +498,8 @@ public class PwmApplication
 
         pwmScheduler.shutdown();
 
-        LOGGER.info( () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " closed for bidness, cya!" );
+        LOGGER.info( () -> PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION
+                + " closed for bidness, cya!", () -> TimeDuration.fromCurrent( startTime ) );
     }
 
     private static void outputKeystore( final PwmApplication pwmApplication ) throws Exception
@@ -719,8 +712,6 @@ public class PwmApplication
     }
 
 
-
-
     private Instant fetchInstallDate( final Instant startupTime )
     {
         if ( localDB != null )
@@ -756,6 +747,11 @@ public class PwmApplication
             }
         }
 
+        if ( pwmApplication.getLocalDB() == null || pwmApplication.getApplicationMode() != PwmApplicationMode.RUNNING )
+        {
+            return DEFAULT_INSTANCE_ID;
+        }
+
         {
             final Optional<String> optionalStoredInstanceID = readAppAttribute( AppAttribute.INSTANCE_ID, String.class );
             if ( optionalStoredInstanceID.isPresent() )
@@ -786,9 +782,9 @@ public class PwmApplication
         return ( SharedHistoryService ) pwmServiceManager.getService( PwmServiceEnum.SharedHistoryManager );
     }
 
-    public IntruderService getIntruderService( )
+    public IntruderSystemService getIntruderSystemService( ) throws PwmUnrecoverableException
     {
-        return ( IntruderService ) pwmServiceManager.getService( PwmServiceEnum.IntruderManager );
+        return ( IntruderSystemService ) pwmServiceManager.getService( PwmServiceEnum.IntruderSystemService );
     }
 
     public LocalDBLogger getLocalDBLogger( )
@@ -808,28 +804,39 @@ public class PwmApplication
 
     public List<PwmService> getPwmServices( )
     {
-        final List<PwmService> pwmServices = new ArrayList<>();
+        final List<PwmService> pwmServices = new ArrayList<>( this.pwmServiceManager.getRunningServices() );
         pwmServices.add( this.localDBLogger );
-        pwmServices.addAll( this.pwmServiceManager.getRunningServices() );
         return Collections.unmodifiableList( pwmServices );
     }
 
-    public List<PwmService> getAppAndDomainPwmServices( )
+    public Map<DomainID, List<PwmService>> getAppAndDomainPwmServices( )
     {
-        final List<PwmService> pwmServices = new ArrayList<>( getPwmServices() );
-        domains().values().forEach( domain -> pwmServices.addAll( domain.getPwmServices() ) );
-        return Collections.unmodifiableList( pwmServices );
+        final Map<DomainID, List<PwmService>> pwmServices = new LinkedHashMap<>();
+
+        for ( final PwmService pwmService : getPwmServices() )
+        {
+            pwmServices.computeIfAbsent( DomainID.systemId(), k -> new ArrayList<>() ).add( pwmService );
+        }
 
+        for ( final PwmDomain pwmDomain : domains().values() )
+        {
+            for ( final PwmService pwmService : pwmDomain.getPwmServices() )
+            {
+                pwmServices.computeIfAbsent( pwmDomain.getDomainID(), k -> new ArrayList<>() ).add( pwmService );
+            }
+        }
+
+        return Collections.unmodifiableMap( pwmServices );
     }
 
     public WordlistService getWordlistService( )
     {
-        return ( WordlistService ) pwmServiceManager.getService( PwmServiceEnum.WordlistManager );
+        return ( WordlistService ) pwmServiceManager.getService( PwmServiceEnum.WordlistService );
     }
 
     public SeedlistService getSeedlistManager( )
     {
-        return ( SeedlistService ) pwmServiceManager.getService( PwmServiceEnum.SeedlistManager );
+        return ( SeedlistService ) pwmServiceManager.getService( PwmServiceEnum.SeedlistService );
     }
 
     public ReportService getReportService( )
@@ -839,22 +846,17 @@ public class PwmApplication
 
     public EmailService getEmailQueue( )
     {
-        return ( EmailService ) pwmServiceManager.getService( PwmServiceEnum.EmailQueueManager );
+        return ( EmailService ) pwmServiceManager.getService( PwmServiceEnum.EmailService );
     }
 
-    public AuditService getAuditManager( )
+    public AuditService getAuditService( )
     {
         return ( AuditService ) pwmServiceManager.getService( PwmServiceEnum.AuditService );
     }
 
-    public SmsQueueManager getSmsQueue( )
-    {
-        return ( SmsQueueManager ) pwmServiceManager.getService( PwmServiceEnum.SmsQueueManager );
-    }
-
-    public PwNotifyService getPwNotifyService( )
+    public SmsQueueService getSmsQueue( )
     {
-        return ( PwNotifyService ) pwmServiceManager.getService( PwmServiceEnum.PwExpiryNotifyService );
+        return ( SmsQueueService ) pwmServiceManager.getService( PwmServiceEnum.SmsQueueManager );
     }
 
     public UrlShortenerService getUrlShortener( )
@@ -894,9 +896,9 @@ public class PwmApplication
         return ( DatabaseService ) pwmServiceManager.getService( PwmServiceEnum.DatabaseService );
     }
 
-    public StatisticsManager getStatisticsManager( )
+    public StatisticsService getStatisticsManager( )
     {
-        return ( StatisticsManager ) pwmServiceManager.getService( PwmServiceEnum.StatisticsManager );
+        return ( StatisticsService ) pwmServiceManager.getService( PwmServiceEnum.StatisticsService );
     }
 
     public SessionStateService getSessionStateService( )
@@ -942,7 +944,7 @@ public class PwmApplication
             final MacroRequest macroRequest
     )
     {
-        final SmsQueueManager smsQueue = getSmsQueue();
+        final SmsQueueService smsQueue = getSmsQueue();
         if ( smsQueue == null )
         {
             LOGGER.error( sessionLabel, () -> "SMS queue is unavailable, unable to send SMS to: " + to );
@@ -1119,4 +1121,33 @@ public class PwmApplication
         }
         return false;
     }
+
+    public enum Condition
+    {
+        RunningMode( ( pwmApplication ) -> pwmApplication.getApplicationMode() == PwmApplicationMode.RUNNING ),
+        LocalDBOpen( ( pwmApplication ) -> pwmApplication.getLocalDB() != null && LocalDB.Status.OPEN == pwmApplication.getLocalDB().status() ),
+        NotInternalInstance( pwmApplication -> !pwmApplication.getPwmEnvironment().isInternalRuntimeInstance() ),;
+
+        private final Function<PwmApplication, Boolean> function;
+
+        Condition( final Function<PwmApplication, Boolean> function )
+        {
+            this.function = function;
+        }
+
+        private boolean matches( final PwmApplication pwmApplication )
+        {
+            return function.apply( pwmApplication );
+        }
+    }
+
+    public boolean checkConditions( final Set<Condition> conditions )
+    {
+        if ( conditions == null )
+        {
+            return true;
+        }
+
+        return conditions.stream().allMatch( ( c ) -> c.matches( this ) );
+    }
 }

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

@@ -210,7 +210,7 @@ public abstract class PwmConstants
             "this password is an memorial of the richard d. kiel memorial abend" );
 
 
-    private static String readPwmConstantsBundle( final String key )
+    public static String readPwmConstantsBundle( final String key )
     {
         return ResourceBundle.getBundle( PwmConstants.class.getName() ).getString( key );
     }

+ 19 - 17
server/src/main/java/password/pwm/PwmDomain.java

@@ -24,6 +24,7 @@ import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSettingScope;
@@ -39,11 +40,11 @@ import password.pwm.svc.PwmServiceManager;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.event.AuditService;
 import password.pwm.svc.httpclient.HttpClientService;
-import password.pwm.svc.intruder.IntruderService;
+import password.pwm.svc.intruder.IntruderDomainService;
 import password.pwm.svc.pwnotify.PwNotifyService;
 import password.pwm.svc.secure.DomainSecureService;
 import password.pwm.svc.sessiontrack.SessionTrackService;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsService;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.userhistory.UserHistoryService;
 import password.pwm.svc.wordlist.SharedHistoryService;
@@ -51,8 +52,8 @@ import password.pwm.util.DailySummaryJob;
 import password.pwm.util.PwmScheduler;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.operations.CrService;
-import password.pwm.util.operations.OtpService;
+import password.pwm.svc.cr.CrService;
+import password.pwm.svc.otp.OtpService;
 
 import java.time.Instant;
 import java.util.List;
@@ -71,6 +72,7 @@ public class PwmDomain
 
     private final PwmApplication pwmApplication;
     private final DomainID domainID;
+    private final SessionLabel sessionLabel;
 
     private final PwmServiceManager pwmServiceManager;
 
@@ -78,8 +80,9 @@ public class PwmDomain
     {
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
         this.domainID = Objects.requireNonNull( domainID );
+        this.sessionLabel = SessionLabel.builder().domain( domainID.stringValue() ).build();
 
-        this.pwmServiceManager = new PwmServiceManager( pwmApplication, domainID, PwmServiceEnum.forScope( PwmSettingScope.DOMAIN ) );
+        this.pwmServiceManager = new PwmServiceManager( sessionLabel, pwmApplication, domainID, PwmServiceEnum.forScope( PwmSettingScope.DOMAIN ) );
     }
 
     public void initialize()
@@ -93,7 +96,6 @@ public class PwmDomain
         {
             final ExecutorService executorService = PwmScheduler.makeSingleThreadExecutorService( getPwmApplication(), DailySummaryJob.class );
             pwmApplication.getPwmScheduler().scheduleDailyZuluZeroStartJob( new DailySummaryJob( this ), executorService, TimeDuration.ZERO );
-            new DailySummaryJob( this ).run();
         }
 
         LOGGER.trace( () -> "completed initializing domain " + domainID.stringValue(), () -> TimeDuration.fromCurrent( startTime ) );
@@ -109,7 +111,7 @@ public class PwmDomain
         return pwmApplication.getApplicationMode();
     }
 
-    public StatisticsManager getStatisticsManager( )
+    public StatisticsService getStatisticsManager( )
     {
         return pwmApplication.getStatisticsManager();
     }
@@ -154,9 +156,9 @@ public class PwmDomain
         return ( LdapConnectionService ) pwmServiceManager.getService( PwmServiceEnum.LdapConnectionService );
     }
 
-    public AuditService getAuditManager()
+    public AuditService getAuditService()
     {
-        return pwmApplication.getAuditManager();
+        return pwmApplication.getAuditService();
     }
 
     public SessionTrackService getSessionTrackService()
@@ -164,12 +166,12 @@ public class PwmDomain
         return pwmApplication.getSessionTrackService();
     }
 
-    public ChaiUser getProxiedChaiUser( final UserIdentity userIdentity )
+    public ChaiUser getProxiedChaiUser( final SessionLabel sessionLabel, final UserIdentity userIdentity )
             throws PwmUnrecoverableException
     {
         try
         {
-            final ChaiProvider proxiedProvider = getProxyChaiProvider( userIdentity.getLdapProfileID() );
+            final ChaiProvider proxiedProvider = getProxyChaiProvider( sessionLabel, userIdentity.getLdapProfileID() );
             return proxiedProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() );
         }
         catch ( final ChaiUnavailableException e )
@@ -178,16 +180,16 @@ public class PwmDomain
         }
     }
 
-    public ChaiProvider getProxyChaiProvider( final String identifier )
+    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final String identifier )
             throws PwmUnrecoverableException
     {
         Objects.requireNonNull( identifier );
-        return getLdapConnectionService().getProxyChaiProvider( identifier );
+        return getLdapConnectionService().getProxyChaiProvider( sessionLabel, identifier );
     }
 
     public List<PwmService> getPwmServices( )
     {
-        return pwmApplication.getPwmServices();
+        return pwmServiceManager.getRunningServices();
     }
 
     public UserSearchEngine getUserSearchEngine()
@@ -200,9 +202,9 @@ public class PwmDomain
         return pwmApplication.getHttpClientService();
     }
 
-    public IntruderService getIntruderManager()
+    public IntruderDomainService getIntruderService()
     {
-        return pwmApplication.getIntruderService();
+        return ( IntruderDomainService ) pwmServiceManager.getService( PwmServiceEnum.IntruderDomainService );
     }
 
     public TokenService getTokenService()
@@ -222,7 +224,7 @@ public class PwmDomain
 
     public PwNotifyService getPwNotifyService()
     {
-        return pwmApplication.getPwNotifyService();
+        return ( PwNotifyService ) pwmServiceManager.getService( PwmServiceEnum.PwExpiryNotifyService );
     }
 
     public ResourceServletService getResourceServletService( )

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

@@ -35,7 +35,6 @@ public class DomainID implements Comparable<DomainID>, Serializable
 {
     public static final List<String> DOMAIN_RESERVED_WORDS = List.of( "system", "private", "public", "pwm", "sspr", "domain", "profile", "password" );
     public static final DomainID DOMAIN_ID_DEFAULT = create( "default" );
-    public static final DomainID DOMAIN_ID_PLACEHOLDER = create( "default" );
 
     private static final String SYSTEM_ID = "system";
     private static final DomainID SYSTEM_DOMAIN_ID = new DomainID( SYSTEM_ID );

+ 13 - 8
server/src/main/java/password/pwm/bean/SessionLabel.java

@@ -22,6 +22,8 @@ package password.pwm.bean;
 
 import lombok.Builder;
 import lombok.Value;
+import password.pwm.PwmConstants;
+import password.pwm.svc.PwmService;
 
 import java.io.Serializable;
 
@@ -29,16 +31,10 @@ import java.io.Serializable;
 @Builder( toBuilder = true )
 public class SessionLabel implements Serializable
 {
-    public static final SessionLabel SYSTEM_LABEL = null;
     public static final String SESSION_LABEL_SESSION_ID = "#";
-    public static final SessionLabel PW_EXP_NOTICE_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "pwExpireNotice" ).build();
-    public static final SessionLabel TOKEN_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "token" ).build();
+    public static final SessionLabel SYSTEM_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( PwmConstants.PWM_APP_NAME ).build();
+    public static final SessionLabel TEST_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "test" ).build();
     public static final SessionLabel CLI_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "cli" ).build();
-    public static final SessionLabel HEALTH_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "health" ).build();
-    public static final SessionLabel REPORTING_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "reporting" ).build();
-    public static final SessionLabel AUDITING_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "auditing" ).build();
-    public static final SessionLabel TELEMETRY_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "telemetry" ).build();
-    public static final SessionLabel PWNOTIFY_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "pwnotify" ).build();
     public static final SessionLabel CONTEXT_SESSION_LABEL = SessionLabel.builder().sessionID( SESSION_LABEL_SESSION_ID ).username( "context" ).build();
 
     private final String sessionID;
@@ -49,4 +45,13 @@ public class SessionLabel implements Serializable
     private final String sourceHostname;
     private final String profile;
     private final String domain;
+
+    public static SessionLabel forPwmService( final PwmService pwmService, final DomainID domainID )
+    {
+        return SessionLabel.builder()
+                .sessionID( SESSION_LABEL_SESSION_ID )
+                .username( pwmService.getClass().getSimpleName() )
+                .domain( domainID.stringValue() )
+                .build();
+    }
 }

+ 9 - 9
server/src/main/java/password/pwm/bean/UserIdentity.java

@@ -200,7 +200,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         }
     }
 
-    public static UserIdentity fromDelimitedKey( final String key )
+    public static UserIdentity fromDelimitedKey( final SessionLabel sessionLabel, final String key )
             throws PwmUnrecoverableException
     {
         JavaHelper.requireNonEmpty( key );
@@ -211,7 +211,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         }
         catch ( final Exception e )
         {
-            LOGGER.trace( () -> "unable to deserialize UserIdentity: " + key + " using JSON method: " + e.getMessage() );
+            LOGGER.trace( sessionLabel, () -> "unable to deserialize UserIdentity: " + key + " using JSON method: " + e.getMessage() );
         }
 
         // old style
@@ -253,7 +253,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
      * @deprecated  Should be used by calling {@link #fromDelimitedKey(String)} or {@link #fromObfuscatedKey(String, PwmApplication)}.
      */
     @Deprecated
-    public static UserIdentity fromKey( final String key, final PwmApplication pwmApplication )
+    public static UserIdentity fromKey( final SessionLabel sessionLabel, final String key, final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {
         JavaHelper.requireNonEmpty( key );
@@ -263,10 +263,10 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
             return fromObfuscatedKey( key, pwmApplication );
         }
 
-        return fromDelimitedKey( key );
+        return fromDelimitedKey( sessionLabel, key );
     }
 
-    public boolean canonicalEquals( final UserIdentity otherIdentity, final PwmApplication pwmApplication )
+    public boolean canonicalEquals( final SessionLabel sessionLabel, final UserIdentity otherIdentity, final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {
         if ( otherIdentity == null )
@@ -274,8 +274,8 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
             return false;
         }
 
-        final UserIdentity thisCanonicalIdentity = this.canonicalized( pwmApplication );
-        final UserIdentity otherCanonicalIdentity = otherIdentity.canonicalized( pwmApplication );
+        final UserIdentity thisCanonicalIdentity = this.canonicalized( sessionLabel, pwmApplication );
+        final UserIdentity otherCanonicalIdentity = otherIdentity.canonicalized( sessionLabel, pwmApplication );
         return thisCanonicalIdentity.equals( otherCanonicalIdentity );
     }
 
@@ -308,7 +308,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         return COMPARATOR.compare( this, otherIdentity );
     }
 
-    public UserIdentity canonicalized( final PwmApplication pwmApplication )
+    public UserIdentity canonicalized( final SessionLabel sessionLabel, final PwmApplication pwmApplication )
             throws PwmUnrecoverableException
     {
         if ( this.canonical )
@@ -316,7 +316,7 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
             return this;
         }
 
-        final ChaiUser chaiUser = pwmApplication.domains().get( this.getDomainID() ).getProxiedChaiUser( this );
+        final ChaiUser chaiUser = pwmApplication.domains().get( this.getDomainID() ).getProxiedChaiUser( sessionLabel, this );
         final String userDN;
         try
         {

+ 7 - 40
server/src/main/java/password/pwm/config/AppConfig.java

@@ -25,13 +25,12 @@ import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.PrivateKeyCertificate;
 import password.pwm.config.option.CertificateMatchingMode;
+import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.profile.EmailServerProfile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.data.UserPermission;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
@@ -40,7 +39,6 @@ import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmRandom;
 import password.pwm.util.secure.PwmSecurityKey;
 
 import java.security.cert.X509Certificate;
@@ -208,6 +206,11 @@ public class AppConfig implements SettingReader
         return settingReader.readLocalizedBundle( className, keyName );
     }
 
+    public List<DataStorageMethod> readGenericStorageLocations( final PwmSetting setting )
+    {
+        return settingReader.readGenericStorageLocations( setting );
+    }
+
     private class ConfigurationSuppliers
     {
         private final Supplier<Map<String, String>> appPropertyOverrides = new LazySupplier<>( () ->
@@ -216,43 +219,7 @@ public class AppConfig implements SettingReader
 
         private final LazySupplier.CheckedSupplier<PwmSecurityKey, PwmUnrecoverableException> pwmSecurityKey
                 = LazySupplier.checked( () ->
-        {
-            final PasswordData configValue = settingReader.readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY );
-
-            if ( configValue == null || configValue.getStringValue().isEmpty() )
-            {
-                final String errorMsg = "Security Key value is not configured, will generate temp value for use by runtime instance";
-                final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                LOGGER.warn( errorInfo::toDebugStr );
-                if ( tempInstanceKey == null )
-                {
-                    tempInstanceKey = new PwmSecurityKey( PwmRandom.getInstance().alphaNumericString( 1024 ) );
-                }
-                return tempInstanceKey;
-            }
-            else
-            {
-                final int minSecurityKeyLength = Integer.parseInt( readAppProperty( AppProperty.SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH ) );
-                if ( configValue.getStringValue().length() < minSecurityKeyLength )
-                {
-                    final String errorMsg = "Security Key must be greater than 32 characters in length";
-                    final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                    throw new PwmUnrecoverableException( errorInfo );
-                }
-
-                try
-                {
-                    return new PwmSecurityKey( configValue.getStringValue() );
-                }
-                catch ( final Exception e )
-                {
-                    final String errorMsg = "unexpected error generating Security Key crypto: " + e.getMessage();
-                    final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                    LOGGER.error( errorInfo::toDebugStr, e );
-                    throw new PwmUnrecoverableException( errorInfo );
-                }
-            }
-        } );
+                settingReader.readSecurityKey( PwmSetting.PWM_SECURITY_KEY, AppConfig.this ) );
 
         private final Supplier<Map<Locale, String>> localeFlagMap = new LazySupplier<>( () ->
         {

+ 0 - 86
server/src/main/java/password/pwm/config/ConfigurationUtil.java

@@ -1,86 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2020 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;
-
-import password.pwm.config.option.DataStorageMethod;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Static helper methods for reading {@link DomainConfig} values.
- */
-public class ConfigurationUtil
-{
-    private ConfigurationUtil()
-    {
-    }
-
-    public static List<DataStorageMethod> getCrReadPreference( final DomainConfig domainConfig )
-    {
-        final List<DataStorageMethod> readPreferences = new ArrayList<>(
-                domainConfig.getResponseStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE ) );
-
-        if ( readPreferences.size() == 1 && readPreferences.iterator().next() == DataStorageMethod.AUTO )
-        {
-            readPreferences.clear();
-            if ( domainConfig.getAppConfig().hasDbConfigured() )
-            {
-                readPreferences.add( DataStorageMethod.DB );
-            }
-            else
-            {
-                readPreferences.add( DataStorageMethod.LDAP );
-            }
-        }
-
-
-        if ( domainConfig.readSettingAsBoolean( PwmSetting.EDIRECTORY_USE_NMAS_RESPONSES ) )
-        {
-            readPreferences.add( DataStorageMethod.NMAS );
-        }
-
-        return Collections.unmodifiableList( readPreferences );
-    }
-
-    public static List<DataStorageMethod> getCrWritePreference( final DomainConfig domainConfig )
-    {
-        final List<DataStorageMethod> writeMethods = new ArrayList<>( domainConfig.getResponseStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE ) );
-        if ( writeMethods.size() == 1 && writeMethods.get( 0 ) == DataStorageMethod.AUTO )
-        {
-            writeMethods.clear();
-            if ( domainConfig.getAppConfig().hasDbConfigured() )
-            {
-                writeMethods.add( DataStorageMethod.DB );
-            }
-            else
-            {
-                writeMethods.add( DataStorageMethod.LDAP );
-            }
-        }
-        if ( domainConfig.readSettingAsBoolean( PwmSetting.EDIRECTORY_STORE_NMAS_RESPONSES ) )
-        {
-            writeMethods.add( DataStorageMethod.NMAS );
-        }
-        return writeMethods;
-    }
-}

+ 56 - 60
server/src/main/java/password/pwm/config/DomainConfig.java

@@ -47,19 +47,20 @@ import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.NamedSecretData;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.config.value.data.UserPermission;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
+import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmRandom;
+import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmSecurityKey;
+import password.pwm.util.secure.SecureEngine;
 
+import java.io.StringWriter;
 import java.security.cert.X509Certificate;
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -106,7 +107,6 @@ public class DomainConfig implements SettingReader
         return getDomainID().stringValue().equals( adminDomainStr );
     }
 
-
     public List<FormConfiguration> readSettingAsForm( final PwmSetting setting )
     {
         return settingReader.readSettingAsForm( setting );
@@ -203,7 +203,6 @@ public class DomainConfig implements SettingReader
         return StoredConfigurationUtil.profilesForSetting( this.getDomainID(), PwmSetting.PASSWORD_PROFILE_LIST, storedConfiguration );
     }
 
-
     public List<String> readSettingAsStringArray( final PwmSetting setting )
     {
         return settingReader.readSettingAsStringArray( setting );
@@ -234,31 +233,15 @@ public class DomainConfig implements SettingReader
         return settingReader.readSettingAsPrivateKey( setting );
     }
 
-    private PwmSecurityKey tempInstanceKey = null;
-
     public PwmSecurityKey getSecurityKey( ) throws PwmUnrecoverableException
     {
+        //return configurationSuppliers.pwmSecurityKey.call();
         return getAppConfig().getSecurityKey();
     }
 
-    public List<DataStorageMethod> getResponseStorageLocations( final PwmSetting setting )
-    {
-        return getGenericStorageLocations( setting );
-    }
-
-    public List<DataStorageMethod> getOtpSecretStorageLocations( final PwmSetting setting )
-    {
-        return getGenericStorageLocations( setting );
-    }
-
-    private List<DataStorageMethod> getGenericStorageLocations( final PwmSetting setting )
+    public List<DataStorageMethod> readGenericStorageLocations( final PwmSetting setting )
     {
-        final String input = readSettingAsString( setting );
-
-        return Arrays.stream( input.split( "-" ) )
-                .map( s ->  JavaHelper.readEnumFromString( DataStorageMethod.class, s ) )
-                .flatMap( Optional::stream )
-                .collect( Collectors.toUnmodifiableList() );
+        return settingReader.readGenericStorageLocations( setting );
     }
 
     public LdapProfile getDefaultLdapProfile( ) throws PwmUnrecoverableException
@@ -300,44 +283,22 @@ public class DomainConfig implements SettingReader
             );
         } );
 
+
         private final LazySupplier.CheckedSupplier<PwmSecurityKey, PwmUnrecoverableException> pwmSecurityKey
                 = LazySupplier.checked( () ->
         {
-            final PasswordData configValue = readSettingAsPassword( PwmSetting.PWM_SECURITY_KEY );
-
-            if ( configValue == null || configValue.getStringValue().isEmpty() )
-            {
-                final String errorMsg = "Security Key value is not configured, will generate temp value for use by runtime instance";
-                final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                LOGGER.warn( errorInfo::toDebugStr );
-                if ( tempInstanceKey == null )
-                {
-                    tempInstanceKey = new PwmSecurityKey( PwmRandom.getInstance().alphaNumericString( 1024 ) );
-                }
-                return tempInstanceKey;
-            }
-            else
-            {
-                final int minSecurityKeyLength = Integer.parseInt( readAppProperty( AppProperty.SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH ) );
-                if ( configValue.getStringValue().length() < minSecurityKeyLength )
-                {
-                    final String errorMsg = "Security Key must be greater than 32 characters in length";
-                    final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                    throw new PwmUnrecoverableException( errorInfo );
-                }
-
-                try
-                {
-                    return new PwmSecurityKey( configValue.getStringValue() );
-                }
-                catch ( final Exception e )
-                {
-                    final String errorMsg = "unexpected err0or generating Security Key crypto: " + e.getMessage();
-                    final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
-                    LOGGER.error( errorInfo::toDebugStr, e );
-                    throw new PwmUnrecoverableException( errorInfo );
-                }
-            }
+            final StringWriter keyData = new StringWriter();
+            keyData.append( domainID.stringValue() );
+            CollectionUtil.iteratorToStream( getStoredConfiguration().keys() )
+                    .filter( key -> Objects.equals( key.getDomainID(), getDomainID() ) )
+                    .sorted()
+                    .map( storedConfiguration::readStoredValue )
+                    .flatMap( Optional::stream )
+                    .forEach( value -> keyData.append( value.valueHash() ) );
+
+            final String hashedData = SecureEngine.hash( keyData.toString(), PwmHashAlgorithm.SHA512 );
+            final PwmSecurityKey domainKey = new PwmSecurityKey( hashedData );
+            return getAppConfig().getSecurityKey().add( domainKey );
         } );
     }
 
@@ -412,4 +373,39 @@ public class DomainConfig implements SettingReader
     {
         return getDomainID().toString();
     }
+
+    public List<DataStorageMethod> getCrReadPreference()
+    {
+        return calculateMethods( PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE, PwmSetting.EDIRECTORY_USE_NMAS_RESPONSES );
+    }
+
+    public List<DataStorageMethod> getCrWritePreference()
+    {
+        return calculateMethods( PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE, PwmSetting.EDIRECTORY_STORE_NMAS_RESPONSES );
+    }
+
+    private List<DataStorageMethod> calculateMethods(
+            final PwmSetting setting,
+            final PwmSetting addNmasSetting
+    )
+    {
+        final List<DataStorageMethod> methods = new ArrayList<>( this.readGenericStorageLocations( setting ) );
+        if ( methods.size() == 1 && methods.get( 0 ) == DataStorageMethod.AUTO )
+        {
+            methods.clear();
+            if ( getAppConfig().hasDbConfigured() )
+            {
+                methods.add( DataStorageMethod.DB );
+            }
+            else
+            {
+                methods.add( DataStorageMethod.LDAP );
+            }
+        }
+        if ( this.readSettingAsBoolean( addNmasSetting ) )
+        {
+            methods.add( DataStorageMethod.NMAS );
+        }
+        return Collections.unmodifiableList( methods );
+    }
 }

+ 41 - 29
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -61,31 +61,37 @@ public enum PwmSetting
             "domain.list", PwmSettingSyntax.DOMAIN, PwmSettingCategory.DOMAINS ),
     DOMAIN_SYSTEM_ADMIN(
             "domain.system.adminDomain", PwmSettingSyntax.STRING, PwmSettingCategory.DOMAINS ),
+    DOMAIN_DOMAIN_PATHS(
+            "domain.system.domainPaths", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.DOMAINS ),
 
     // application settings
     APP_PROPERTY_OVERRIDES(
             "pwm.appProperty.overrides", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.APPLICATION ),
+    HIDE_CONFIGURATION_HEALTH_WARNINGS(
+            "display.hideConfigHealthWarnings", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.APPLICATION ),
 
     // domain settings
     PWM_SITE_URL(
             "pwm.selfURL", PwmSettingSyntax.STRING, PwmSettingCategory.APPLICATION ),
-    PUBLISH_STATS_ENABLE(
-            "pwm.publishStats.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.TELEMETRY ),
-    PUBLISH_STATS_SITE_DESCRIPTION(
-            "pwm.publishStats.siteDescription", PwmSettingSyntax.STRING, PwmSettingCategory.TELEMETRY ),
+
+
+    // domain settings
     URL_FORWARD(
-            "pwm.forwardURL", PwmSettingSyntax.STRING, PwmSettingCategory.GENERAL ),
+            "pwm.forwardURL", PwmSettingSyntax.STRING, PwmSettingCategory.URL_SETTINGS ),
     URL_LOGOUT(
-            "pwm.logoutURL", PwmSettingSyntax.STRING, PwmSettingCategory.GENERAL ),
+            "pwm.logoutURL", PwmSettingSyntax.STRING, PwmSettingCategory.URL_SETTINGS ),
     URL_HOME(
-            "pwm.homeURL", PwmSettingSyntax.STRING, PwmSettingCategory.GENERAL ),
+            "pwm.homeURL", PwmSettingSyntax.STRING, PwmSettingCategory.URL_SETTINGS ),
     URL_INTRO(
-            "pwm.introURL", PwmSettingSyntax.SELECT, PwmSettingCategory.GENERAL ),
-    IDLE_TIMEOUT_SECONDS(
-            "idleTimeoutSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.GENERAL ),
-    HIDE_CONFIGURATION_HEALTH_WARNINGS(
-            "display.hideConfigHealthWarnings", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.GENERAL ),
+            "pwm.introURL", PwmSettingSyntax.SELECT, PwmSettingCategory.URL_SETTINGS ),
+    DOMAIN_HOSTS(
+            "domain.hosts", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.URL_SETTINGS ),
 
+    // telemetry
+    PUBLISH_STATS_ENABLE(
+            "pwm.publishStats.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.TELEMETRY ),
+    PUBLISH_STATS_SITE_DESCRIPTION(
+            "pwm.publishStats.siteDescription", PwmSettingSyntax.STRING, PwmSettingCategory.TELEMETRY ),
 
     KNOWN_LOCALES(
             "knownLocales", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.LOCALIZATION ),
@@ -129,6 +135,8 @@ public enum PwmSetting
             "display.idleTimeout", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES ),
     PASSWORD_SHOW_STRENGTH_METER(
             "password.showStrengthMeter", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UI_FEATURES ),
+    IDLE_TIMEOUT_SECONDS(
+            "idleTimeoutSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.UI_FEATURES ),
     DISPLAY_CSS_CUSTOM_STYLE(
             "display.css.customStyleLocation", PwmSettingSyntax.STRING, PwmSettingCategory.UI_WEB ),
     DISPLAY_CSS_CUSTOM_MOBILE_STYLE(
@@ -144,7 +152,7 @@ public enum PwmSetting
 
     // change password
     CHANGE_PASSWORD_PROFILE_LIST(
-            "changePassword.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "changePassword.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     CHANGE_PASSWORD_ENABLE(
             "changePassword.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.CHANGE_PASSWORD_SETTINGS ),
     QUERY_MATCH_CHANGE_PASSWORD(
@@ -180,7 +188,7 @@ public enum PwmSetting
     ACCOUNT_INFORMATION_ENABLED(
             "display.accountInformation", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACCOUNT_INFO_SETTINGS ),
     ACCOUNT_INFORMATION_PROFILE_LIST(
-            "accountInfo.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "accountInfo.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     ACCOUNT_INFORMATION_QUERY_MATCH(
             "accountInfo.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ACCOUNT_INFO_PROFILE ),
     ACCOUNT_INFORMATION_HISTORY(
@@ -193,7 +201,7 @@ public enum PwmSetting
 
     // delete info
     DELETE_ACCOUNT_PROFILE_LIST(
-            "deleteAccount.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "deleteAccount.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     DELETE_ACCOUNT_ENABLE(
             "deleteAccount.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.DELETE_ACCOUNT_SETTINGS ),
     DELETE_ACCOUNT_PERMISSION(
@@ -291,7 +299,7 @@ public enum PwmSetting
 
     // ldap global settings
     LDAP_PROFILE_LIST(
-            "ldap.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "ldap.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     LDAP_IDLE_TIMEOUT(
             "ldap.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.LDAP_GLOBAL ),
     DEFAULT_OBJECT_CLASSES(
@@ -312,7 +320,7 @@ public enum PwmSetting
 
     // New multiple email settings
     EMAIL_SERVERS(
-            "email.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.APPLICATION ),
+            "email.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_SYSTEM ),
     EMAIL_SERVER_ADDRESS(
             "email.smtp.address", PwmSettingSyntax.STRING, PwmSettingCategory.EMAIL_SERVERS ),
     EMAIL_SERVER_TYPE(
@@ -451,7 +459,7 @@ public enum PwmSetting
     PASSWORD_POLICY_CASE_SENSITIVITY(
             "password.policy.caseSensitivity", PwmSettingSyntax.SELECT, PwmSettingCategory.PASSWORD_GLOBAL ),
     PASSWORD_PROFILE_LIST(
-            "password.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "password.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
 
 
     // wordlist settings
@@ -606,7 +614,7 @@ public enum PwmSetting
     INTRUDER_ENABLE(
             "intruder.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.INTRUDER_SETTINGS ),
     INTRUDER_STORAGE_METHOD(
-            "intruder.storageMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.INTRUDER_SETTINGS ),
+            "intruder.storageMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.INTRUDER_SYSTEM_SETTINGS ),
     SECURITY_SIMULATE_LDAP_BAD_PASSWORD(
             "security.ldap.simulateBadPassword", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.INTRUDER_SETTINGS ),
 
@@ -653,7 +661,7 @@ public enum PwmSetting
 
     // OTP
     OTP_PROFILE_LIST(
-            "otp.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "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(
@@ -741,7 +749,7 @@ public enum PwmSetting
 
     // challenge policy profile
     CHALLENGE_PROFILE_LIST(
-            "challenge.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "challenge.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     CHALLENGE_POLICY_QUERY_MATCH(
             "challenge.policy.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.CHALLENGE_POLICY ),
     CHALLENGE_RANDOM_CHALLENGES(
@@ -778,7 +786,7 @@ public enum PwmSetting
 
     // recovery profile
     RECOVERY_PROFILE_LIST(
-            "recovery.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "recovery.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     RECOVERY_PROFILE_QUERY_MATCH(
             "recovery.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.RECOVERY_DEF ),
     RECOVERY_VERIFICATION_METHODS(
@@ -844,7 +852,7 @@ public enum PwmSetting
             "newUser.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.NEWUSER_SETTINGS ),
 
     NEWUSER_PROFILE_LIST(
-            "newUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "newUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
 
     NEWUSER_FORM(
             "newUser.form", PwmSettingSyntax.FORM, PwmSettingCategory.NEWUSER_PROFILE ),
@@ -917,7 +925,7 @@ public enum PwmSetting
             "activateUser.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.ACTIVATION_SETTINGS ),
 
     ACTIVATE_USER_PROFILE_LIST(
-            "activateUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "activateUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
 
     ACTIVATE_USER_QUERY_MATCH(
             "activateUser.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ACTIVATION_PROFILE ),
@@ -936,7 +944,7 @@ public enum PwmSetting
     UPDATE_PROFILE_ENABLE(
             "updateAttributes.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.UPDATE_SETTINGS ),
     UPDATE_PROFILE__PROFILE_LIST(
-            "updateAttributes.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "updateAttributes.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     UPDATE_PROFILE_QUERY_MATCH(
             "updateAttributes.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.UPDATE_PROFILE ),
     UPDATE_PROFILE_WRITE_ATTRIBUTES(
@@ -980,7 +988,7 @@ public enum PwmSetting
     PEOPLE_SEARCH_PUBLIC_PROFILE(
             "peopleSearch.public.profile", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH_SETTINGS ),
     PEOPLESEARCH_PROFILE_LIST(
-            "peopleSearch.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "peopleSearch.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     PEOPLE_SEARCH_QUERY_MATCH(
             "peopleSearch.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_SEARCH_FORM(
@@ -1059,7 +1067,7 @@ public enum PwmSetting
     HELPDESK_ENABLE(
             "helpdesk.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.HELPDESK_SETTINGS ),
     HELPDESK_PROFILE_LIST(
-            "helpdesk.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+            "helpdesk.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL_DOMAIN ),
     HELPDESK_PROFILE_QUERY_MATCH(
             "helpdesk.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.HELPDESK_BASE ),
     HELPDESK_SEARCH_FORM(
@@ -1451,9 +1459,13 @@ public enum PwmSetting
 
         static <T> T referenceForTempleSet(
                 final List<TemplateSetReference<T>> templateSetReferences,
-                final PwmSettingTemplateSet pwmSettingTemplate
+                final PwmSettingTemplateSet pwmSettingTemplateSet
         )
         {
+            final PwmSettingTemplateSet effectiveTemplateSet = pwmSettingTemplateSet == null
+                    ? PwmSettingTemplateSet.getDefault()
+                    : pwmSettingTemplateSet;
+
             if ( templateSetReferences == null || templateSetReferences.isEmpty() )
             {
                 throw new IllegalStateException( "templateSetReferences can not be null" );
@@ -1469,7 +1481,7 @@ public enum PwmSetting
                 for ( final TemplateSetReference<T> templateSetReference : templateSetReferences )
                 {
                     final Set<PwmSettingTemplate> temporarySet = CollectionUtil.copiedEnumSet( templateSetReference.getSettingTemplates(), PwmSettingTemplate.class );
-                    temporarySet.retainAll( pwmSettingTemplate.getTemplates() );
+                    temporarySet.retainAll( effectiveTemplateSet.getTemplates() );
                     final int matchCount = temporarySet.size();
                     if ( matchCount == matchCountExamSize )
                     {

+ 6 - 4
server/src/main/java/password/pwm/config/PwmSettingCategory.java

@@ -78,9 +78,9 @@ public enum PwmSettingCategory
     NODES( SYSTEM ),
 
     DOMAIN( SETTINGS ),
-    GENERAL( DOMAIN ),
-    LOCALIZATION( DOMAIN ),
-    TELEMETRY( DOMAIN ),
+    URL_SETTINGS( SETTINGS ),
+    LOCALIZATION( SETTINGS ),
+    TELEMETRY( SETTINGS ),
 
     AUDITING( SETTINGS ),
     AUDIT_CONFIG( AUDITING ),
@@ -91,6 +91,7 @@ public enum PwmSettingCategory
     CAPTCHA( SETTINGS ),
 
     INTRUDER( SETTINGS ),
+    INTRUDER_SYSTEM_SETTINGS( INTRUDER ),
     INTRUDER_SETTINGS( INTRUDER ),
     INTRUDER_TIMEOUTS( INTRUDER ),
 
@@ -201,7 +202,8 @@ public enum PwmSettingCategory
     DELETE_ACCOUNT_SETTINGS( DELETE_ACCOUNT ),
     DELETE_ACCOUNT_PROFILE( DELETE_ACCOUNT ),
 
-    INTERNAL( SETTINGS ),;
+    INTERNAL_DOMAIN( SETTINGS ),
+    INTERNAL_SYSTEM( SETTINGS ),;
 
     private static final Comparator<PwmSettingCategory> MENU_LOCATION_COMPARATOR = Comparator.comparing(
             ( pwmSettingCategory ) -> pwmSettingCategory.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE ) );

+ 5 - 0
server/src/main/java/password/pwm/config/PwmSettingFlag.java

@@ -37,6 +37,11 @@ public enum PwmSettingFlag
     /* No Default - Makes the setting UI act as if there is not a default to reset to */
     NoDefault,
 
+    /* Only shown if multi-domain is enabled */
+    MultiDomain,
+
+    ReloadEditorOnModify,
+
     Select_AllowUserInput,
 
     Permission_HideGroups,

+ 5 - 0
server/src/main/java/password/pwm/config/PwmSettingMetaDataReader.java

@@ -167,6 +167,11 @@ public class PwmSettingMetaDataReader
         }
     }
 
+    /**
+     * Not required for normal operation, but executing this gets all the enum values poopulated form XML source.  If run prior to users accessing the settings
+     * module (particularly the config editor) it will increase the initial load performance significantly.  There are no side effects to calling this operation
+     * other than cache population.
+     */
     public static void initCache()
     {
         final Instant startTime = Instant.now();

+ 53 - 0
server/src/main/java/password/pwm/config/StoredSettingReader.java

@@ -20,9 +20,11 @@
 
 package password.pwm.config;
 
+import password.pwm.AppProperty;
 import password.pwm.bean.DomainID;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.PrivateKeyCertificate;
+import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.profile.Profile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.stored.StoredConfigKey;
@@ -38,16 +40,21 @@ import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.NamedSecretData;
 import password.pwm.config.value.data.RemoteWebServiceConfiguration;
 import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmRandom;
+import password.pwm.util.secure.PwmSecurityKey;
 
 import java.lang.reflect.InvocationTargetException;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -177,6 +184,16 @@ public class StoredSettingReader implements SettingReader
         return ValueTypeConverter.valueToNamedPassword( readSetting( setting ) );
     }
 
+    public List<DataStorageMethod> readGenericStorageLocations( final PwmSetting setting )
+    {
+        final String input = readSettingAsString( setting );
+
+        return Arrays.stream( input.split( "-" ) )
+                .map( s ->  JavaHelper.readEnumFromString( DataStorageMethod.class, s ) )
+                .flatMap( Optional::stream )
+                .collect( Collectors.toUnmodifiableList() );
+    }
+
     public PrivateKeyCertificate readSettingAsPrivateKey( final PwmSetting setting )
     {
         Objects.requireNonNull( setting );
@@ -342,4 +359,40 @@ public class StoredSettingReader implements SettingReader
         dataCache.customText.put( key, localizedMap );
         return localizedMap;
     }
+
+    public PwmSecurityKey readSecurityKey( final PwmSetting pwmSetting, final AppConfig appConfig )
+            throws PwmUnrecoverableException
+    {
+        final PasswordData configValue = readSettingAsPassword( pwmSetting );
+
+        if ( configValue == null || configValue.getStringValue().isEmpty() )
+        {
+            final String errorMsg = "Security Key value is not configured, will generate temp value for use by runtime instance";
+            final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
+            LOGGER.warn( errorInfo::toDebugStr );
+            return new PwmSecurityKey( PwmRandom.getInstance().alphaNumericString( 1024 ) );
+        }
+        else
+        {
+            final int minSecurityKeyLength = Integer.parseInt( appConfig.readAppProperty( AppProperty.SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH ) );
+            if ( configValue.getStringValue().length() < minSecurityKeyLength )
+            {
+                final String errorMsg = "Security Key must be greater than 32 characters in length";
+                final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
+                throw new PwmUnrecoverableException( errorInfo );
+            }
+
+            try
+            {
+                return new PwmSecurityKey( configValue.getStringValue() );
+            }
+            catch ( final Exception e )
+            {
+                final String errorMsg = "unexpected error generating Security Key crypto: " + e.getMessage();
+                final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_INVALID_SECURITY_KEY, errorMsg );
+                LOGGER.error( errorInfo::toDebugStr, e );
+                throw new PwmUnrecoverableException( errorInfo );
+            }
+        }
+    }
 }

+ 18 - 6
server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -78,7 +78,12 @@ public class UserMatchViewerFunction implements SettingUIFunction
 
         final Instant startSearchTime = Instant.now();
         final int maxResultSize = Integer.parseInt( pwmDomain.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_USER_PERMISSION_MATCH_LIMIT ) );
-        final Collection<UserIdentity> users = discoverMatchingUsers( pwmDomain, maxResultSize, storedConfiguration.newStoredConfiguration(), key );
+        final Collection<UserIdentity> users = discoverMatchingUsers(
+                pwmRequest.getLabel(),
+                pwmDomain,
+                maxResultSize,
+                storedConfiguration.newStoredConfiguration(),
+                key );
         final TimeDuration searchDuration = TimeDuration.fromCurrent( startSearchTime );
 
         final String message = LocaleHelper.getLocalizedMessage(
@@ -96,6 +101,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
     }
 
     public List<UserIdentity> discoverMatchingUsers(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final int maxResultSize,
             final StoredConfiguration storedConfiguration,
@@ -109,7 +115,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
         final List<UserPermission> permissions = ValueTypeConverter.valueToUserPermissions( storedValue );
         final PwmDomain tempDomain = tempApplication.domains().get( key.getDomainID() );
 
-        validateUserPermissionLdapValues( tempDomain, permissions );
+        validateUserPermissionLdapValues( sessionLabel, tempDomain, permissions );
 
         final int maxSearchSeconds = Integer.parseInt( pwmDomain.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_USER_PERMISSION_TIMEOUT_SECONDS ) );
         final TimeDuration maxSearchTime = TimeDuration.of( maxSearchSeconds, TimeDuration.Unit.SECONDS );
@@ -121,6 +127,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
     }
 
     private static void validateUserPermissionLdapValues(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final List<UserPermission> permissions
     )
@@ -132,18 +139,23 @@ public class UserMatchViewerFunction implements SettingUIFunction
             {
                 if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty() )
                 {
-                    testIfLdapDNIsValid( pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
+                    testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
                 }
             }
             else if ( userPermission.getType() == UserPermissionType.ldapGroup )
             {
-                testIfLdapDNIsValid( pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
+                testIfLdapDNIsValid( sessionLabel, pwmDomain, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
             }
         }
     }
 
 
-    private static void testIfLdapDNIsValid( final PwmDomain pwmDomain, final String baseDN, final String profileID )
+    private static void testIfLdapDNIsValid(
+            final SessionLabel sessionLabel,
+            final PwmDomain pwmDomain,
+            final String baseDN,
+            final String profileID
+    )
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final Set<String> profileIDsToTest = new LinkedHashSet<>();
@@ -167,7 +179,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
             ChaiEntry chaiEntry = null;
             try
             {
-                final ChaiProvider proxiedProvider = pwmDomain.getProxyChaiProvider( loopID );
+                final ChaiProvider proxiedProvider = pwmDomain.getProxyChaiProvider( sessionLabel, loopID );
                 chaiEntry = proxiedProvider.getEntryFactory().newChaiEntry( baseDN );
             }
             catch ( final Exception e )

+ 20 - 11
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -27,6 +27,7 @@ import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfiguration;
@@ -60,6 +61,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     }
 
     public Map<String, String> getSelectableContexts(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain
     )
             throws PwmUnrecoverableException
@@ -71,13 +73,14 @@ public class LdapProfile extends AbstractProfile implements Profile
         {
             final String dn = entry.getKey();
             final String label = entry.getValue();
-            final String canonicalDN = readCanonicalDN( pwmDomain, dn );
+            final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
             canonicalValues.put( canonicalDN, label );
         }
         return Collections.unmodifiableMap( canonicalValues );
     }
 
     public List<String> getRootContexts(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain
     )
             throws PwmUnrecoverableException
@@ -86,7 +89,7 @@ public class LdapProfile extends AbstractProfile implements Profile
         final List<String> canonicalValues = new ArrayList<>();
         for ( final String dn : rawValues )
         {
-            final String canonicalDN = readCanonicalDN( pwmDomain, dn );
+            final String canonicalDN = readCanonicalDN( sessionLabel, pwmDomain, dn );
             canonicalValues.add( canonicalDN );
         }
         return Collections.unmodifiableList( canonicalValues );
@@ -112,10 +115,10 @@ public class LdapProfile extends AbstractProfile implements Profile
         return configUsernameAttr != null && configUsernameAttr.length() > 0 ? configUsernameAttr : ldapNamingAttribute;
     }
 
-    public ChaiProvider getProxyChaiProvider( final PwmDomain pwmDomain ) throws PwmUnrecoverableException
+    public ChaiProvider getProxyChaiProvider( final SessionLabel sessionLabel, final PwmDomain pwmDomain ) throws PwmUnrecoverableException
     {
         verifyIsEnabled();
-        return pwmDomain.getProxyChaiProvider( this.getIdentifier() );
+        return pwmDomain.getProxyChaiProvider( sessionLabel, this.getIdentifier() );
     }
 
     @Override
@@ -131,6 +134,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     }
 
     public String readCanonicalDN(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final String dnValue
     )
@@ -163,7 +167,7 @@ public class LdapProfile extends AbstractProfile implements Profile
         {
             try
             {
-                final ChaiProvider chaiProvider = this.getProxyChaiProvider( pwmDomain );
+                final ChaiProvider chaiProvider = this.getProxyChaiProvider( sessionLabel, pwmDomain );
                 final ChaiEntry chaiEntry = chaiProvider.getEntryFactory().newChaiEntry( dnValue );
                 canonicalValue = chaiEntry.readCanonicalDN();
 
@@ -190,23 +194,28 @@ public class LdapProfile extends AbstractProfile implements Profile
         return canonicalValue;
     }
 
-    public UserIdentity getTestUser( final PwmDomain pwmDomain ) throws PwmUnrecoverableException
+    public UserIdentity getTestUser( final SessionLabel sessionLabel, final PwmDomain pwmDomain ) throws PwmUnrecoverableException
     {
-        return readUserIdentity( pwmDomain, PwmSetting.LDAP_TEST_USER_DN );
+        return readUserIdentity( sessionLabel, pwmDomain, PwmSetting.LDAP_TEST_USER_DN );
     }
 
-    public UserIdentity getProxyUser( final PwmDomain pwmDomain ) throws PwmUnrecoverableException
+    public UserIdentity getProxyUser( final SessionLabel sessionLabel, final PwmDomain pwmDomain ) throws PwmUnrecoverableException
     {
-        return readUserIdentity( pwmDomain, PwmSetting.LDAP_PROXY_USER_DN );
+        return readUserIdentity( sessionLabel, pwmDomain, PwmSetting.LDAP_PROXY_USER_DN );
     }
 
-    private UserIdentity readUserIdentity( final PwmDomain pwmDomain, final PwmSetting pwmSetting ) throws PwmUnrecoverableException
+    private UserIdentity readUserIdentity(
+            final SessionLabel sessionLabel,
+            final PwmDomain pwmDomain,
+            final PwmSetting pwmSetting
+    )
+            throws PwmUnrecoverableException
     {
         final String testUserDN = this.readSettingAsString( pwmSetting );
 
         if ( StringUtil.notEmpty( testUserDN ) )
         {
-            return UserIdentity.create( testUserDN, this.getIdentifier(), pwmDomain.getDomainID() ).canonicalized( pwmDomain.getPwmApplication() );
+            return UserIdentity.create( testUserDN, this.getIdentifier(), pwmDomain.getDomainID() ).canonicalized( sessionLabel, pwmDomain.getPwmApplication() );
         }
 
         return null;

+ 13 - 5
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -27,6 +27,7 @@ import password.pwm.AppProperty;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
@@ -34,6 +35,7 @@ import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequestContext;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.password.PasswordUtility;
@@ -70,7 +72,13 @@ public class NewUserProfile extends AbstractProfile implements Profile
         return value != null && !value.isEmpty() ? value : this.getIdentifier();
     }
 
-    public PwmPasswordPolicy getNewUserPasswordPolicy( final PwmDomain pwmDomain, final Locale userLocale )
+    public PwmPasswordPolicy getNewUserPasswordPolicy( final PwmRequestContext pwmRequestContext )
+            throws PwmUnrecoverableException
+    {
+        return getNewUserPasswordPolicy( pwmRequestContext.getSessionLabel(), pwmRequestContext.getPwmDomain(), pwmRequestContext.getLocale() );
+    }
+
+    public PwmPasswordPolicy getNewUserPasswordPolicy( final SessionLabel sessionLabel, final PwmDomain pwmDomain, final Locale locale )
             throws PwmUnrecoverableException
     {
         final DomainConfig domainConfig = pwmDomain.getConfig();
@@ -81,7 +89,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
             newUserPasswordPolicyCache.clear();
         }
 
-        final PwmPasswordPolicy cachedPolicy = newUserPasswordPolicyCache.get( userLocale );
+        final PwmPasswordPolicy cachedPolicy = newUserPasswordPolicyCache.get( locale );
         if ( cachedPolicy != null )
         {
             return cachedPolicy;
@@ -131,7 +139,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
             {
                 try
                 {
-                    final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( ldapProfile.getIdentifier() );
+                    final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( sessionLabel, ldapProfile.getIdentifier() );
                     final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser( lookupDN );
                     final UserIdentity userIdentity = UserIdentity.create( lookupDN, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
                     thePolicy = PasswordUtility.readPasswordPolicyForUser( pwmDomain, null, userIdentity, chaiUser );
@@ -142,7 +150,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
                 }
             }
         }
-        newUserPasswordPolicyCache.put( userLocale, thePolicy );
+        newUserPasswordPolicyCache.put( locale, thePolicy );
         return thePolicy;
     }
 
@@ -190,7 +198,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
                         {
                                 "configured ldap profile for new user profile is invalid.  check setting "
                                         + PwmSetting.NEWUSER_LDAP_PROFILE.toMenuLocationDebug( this.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
-                                }
+                        }
                 ) );
             }
             return ldapProfile;

+ 1 - 1
server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java

@@ -321,7 +321,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
         }
 
         final Locale resolvedLocale = LocaleHelper.localeResolver( locale, policyMetaData.getRuleText().keySet() );
-        return Optional.of( policyMetaData.getRuleText().get( resolvedLocale ) );
+        return Optional.ofNullable( policyMetaData.getRuleText().get( resolvedLocale ) );
     }
 
     public PwmPasswordPolicy merge( final PwmPasswordPolicy otherPolicy )

+ 2 - 2
server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java

@@ -44,7 +44,7 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
-class ConfigurationCleaner
+public class ConfigurationCleaner
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationCleaner.class );
 
@@ -56,7 +56,7 @@ class ConfigurationCleaner
             new RemoveSuperfluousProfileSettings(),
             new RemoveDefaultSettings() );
 
-    static void postProcessStoredConfig(
+    public static void postProcessStoredConfig(
             final StoredConfigurationModifier storedConfiguration
     )
     {

+ 3 - 2
server/src/main/java/password/pwm/config/stored/ConfigurationReader.java

@@ -35,6 +35,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.StringUtil;
@@ -243,7 +244,7 @@ public class ConfigurationReader
             }
         }
 
-        if ( pwmApplication != null && pwmApplication.getAuditManager() != null )
+        if ( pwmApplication != null && pwmApplication.getAuditService() != null )
         {
             auditModifiedSettings( pwmApplication, storedConfiguration, sessionLabel );
         }
@@ -285,7 +286,7 @@ public class ConfigurationReader
             final String finalMsg = modifyMessage;
             LOGGER.trace( () -> "sending audit notice: " + finalMsg );
 
-            pwmApplication.getAuditManager().submit( sessionLabel, new AuditRecordFactory( pwmApplication ).createUserAuditRecord(
+            AuditServiceClient.submit( pwmApplication, sessionLabel, AuditRecordFactory.make( sessionLabel, pwmApplication ).createUserAuditRecord(
                     AuditEvent.MODIFY_CONFIGURATION,
                     userIdentity,
                     sessionLabel,

+ 4 - 2
server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java

@@ -22,6 +22,7 @@ package password.pwm.config.stored;
 
 import password.pwm.PwmConstants;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingFlag;
@@ -72,6 +73,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( StoredConfigXmlSerializer.class );
     private static final String XML_FORMAT_VERSION = "5";
+    private static final SessionLabel SESSION_LABEL = SessionLabel.SYSTEM_LABEL;
     private static final boolean ENABLE_PERF_LOGGING = false;
 
     @Override
@@ -375,7 +377,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 {
                     try
                     {
-                        userIdentity = UserIdentity.fromDelimitedKey( modifyUserValue.get() );
+                        userIdentity = UserIdentity.fromDelimitedKey( SESSION_LABEL, modifyUserValue.get() );
                     }
                     catch ( final Exception e )
                     {
@@ -391,7 +393,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                 {
                     try
                     {
-                        userIdentity = UserIdentity.fromDelimitedKey( metaElement.get().getText().orElse( "" ) );
+                        userIdentity = UserIdentity.fromDelimitedKey( SESSION_LABEL, metaElement.get().getText().orElse( "" ) );
                     }
                     catch ( final DateTimeParseException | PwmUnrecoverableException e )
                     {

+ 11 - 6
server/src/main/java/password/pwm/config/stored/StoredConfigurationUtil.java

@@ -55,13 +55,13 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -254,16 +254,22 @@ public abstract class StoredConfigurationUtil
 
     public static Map<String, String> makeDebugMap(
             final StoredConfiguration storedConfiguration,
-            final List<StoredConfigKey> interestedItems,
+            final Collection<StoredConfigKey> interestedItems,
             final Locale locale
     )
     {
-        return Collections.unmodifiableMap( new TreeMap<>( interestedItems.stream()
+        return Collections.unmodifiableMap( interestedItems.stream()
                 .filter( key -> !key.isRecordType( StoredConfigKey.RecordType.PROPERTY ) )
+                .sorted()
                 .collect( Collectors.toMap(
                         key -> key.getLabel( locale ),
-                        key -> StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key ).toDebugString( locale )
-                ) ) ) );
+                        key -> StoredConfigurationUtil.getValueOrDefault( storedConfiguration, key ).toDebugString( locale ),
+                        ( u, v ) ->
+                        {
+                            throw new IllegalStateException(  String.format( "duplicate key %s", u ) );
+                        },
+                        LinkedHashMap::new
+                ) ) );
     }
 
     public static Set<StoredConfigKey> allPossibleSettingKeysForConfiguration(
@@ -301,7 +307,6 @@ public abstract class StoredConfigurationUtil
 
         return PwmSetting.sortedValues().stream()
                 .filter( ( setting ) -> domainID.inScope( setting.getCategory().getScope() ) )
-                .parallel()
                 .flatMap( function )
                 .collect( Collectors.toUnmodifiableSet() )
                 .stream();

+ 17 - 5
server/src/main/java/password/pwm/config/value/AbstractValue.java

@@ -20,7 +20,6 @@
 
 package password.pwm.config.value;
 
-import password.pwm.PwmConstants;
 import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.error.PwmUnrecoverableException;
@@ -41,6 +40,20 @@ import java.util.Locale;
 
 public abstract class AbstractValue implements StoredValue
 {
+    private static final PwmSecurityKey HASHING_KEY;
+
+    static
+    {
+        try
+        {
+            HASHING_KEY = new PwmSecurityKey( "hash-key" );
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            throw new IllegalStateException( "unable to create internal HASHING_KEY: " + e.getMessage() );
+        }
+    }
+
     private final transient LazySupplier<String> valueHashSupplier = new LazySupplier<>( () -> valueHashComputer( AbstractValue.this ) );
 
     public String toString()
@@ -76,9 +89,8 @@ public abstract class AbstractValue implements StoredValue
     {
         try
         {
-            final PwmSecurityKey testingKey = new PwmSecurityKey( "test" );
             final XmlOutputProcessData xmlOutputProcessData = XmlOutputProcessData.builder()
-                    .pwmSecurityKey( testingKey )
+                    .pwmSecurityKey( HASHING_KEY )
                     .storedValueEncoderMode( StoredValueEncoder.Mode.PLAIN )
                     .build();
             final List<XmlElement> xmlValues = storedValue.toXmlValues( StoredConfigXmlConstants.XML_ELEMENT_VALUE, xmlOutputProcessData );
@@ -86,8 +98,8 @@ public abstract class AbstractValue implements StoredValue
             document.getRootElement().addContent( xmlValues );
             final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             XmlFactory.getFactory().outputDocument( document, byteArrayOutputStream );
-            final String stringToHash = byteArrayOutputStream.toString( PwmConstants.DEFAULT_CHARSET );
-            return SecureEngine.hash( stringToHash, PwmHashAlgorithm.SHA512 );
+            final byte[] bytesToHash = byteArrayOutputStream.toByteArray();
+            return SecureEngine.hash( bytesToHash, PwmHashAlgorithm.SHA512 );
 
         }
         catch ( final IOException | PwmUnrecoverableException e )

+ 2 - 1
server/src/main/java/password/pwm/config/value/FileValue.java

@@ -84,7 +84,8 @@ public class FileValue extends AbstractValue implements StoredValue
         public static FileContent fromEncodedString( final String input )
                 throws IOException
         {
-            return new FileContent( input );
+            final String whitespaceStripped = StringUtil.stripAllWhitespace( input );
+            return new FileContent( whitespaceStripped );
         }
 
         public static FileContent fromBytes( final ImmutableByteArray contents )

+ 4 - 20
server/src/main/java/password/pwm/config/value/ValueTypeConverter.java

@@ -40,7 +40,6 @@ import password.pwm.util.logging.PwmLogger;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -192,20 +191,12 @@ public final class ValueTypeConverter
         }
 
         final List<String> results = new ArrayList<>( ( List<String> ) value.toNativeObject() );
-        for ( final Iterator iter = results.iterator(); iter.hasNext(); )
-        {
-            final Object loopString = iter.next();
-            if ( loopString == null || loopString.toString().length() < 1 )
-            {
-                iter.remove();
-            }
-        }
-        return results;
+        results.removeIf( StringUtil::isEmpty );
+        return List.copyOf( results );
     }
 
     public static List<UserPermission> valueToUserPermissions( final StoredValue value )
     {
-
         Objects.requireNonNull( value );
 
         if ( !( value instanceof UserPermissionValue ) )
@@ -214,15 +205,8 @@ public final class ValueTypeConverter
         }
 
         final List<UserPermission> results = new ArrayList<>( ( List<UserPermission> ) value.toNativeObject() );
-        for ( final Iterator iter = results.iterator(); iter.hasNext(); )
-        {
-            final Object loopString = iter.next();
-            if ( loopString == null || loopString.toString().length() < 1 )
-            {
-                iter.remove();
-            }
-        }
-        return results;
+        results.removeIf( Objects::isNull );
+        return List.copyOf( results );
     }
 
     public static Map<String, List<ChallengeItemConfiguration>> valueToChallengeItems( final StoredValue value )

+ 11 - 10
server/src/main/java/password/pwm/error/PwmError.java

@@ -146,11 +146,11 @@ public enum PwmError
     ERROR_MISSING_PARAMETER(
             5013, "Error_MissingParameter", Collections.emptySet() ),
     ERROR_INTERNAL(
-            5015, "Error_Internal", null, ErrorFlag.ForceLogout ),
+            5015, "Error_Internal", null ),
     ERROR_CANT_MATCH_USER(
             5016, "Error_CantMatchUser", Collections.emptySet() ),
     ERROR_DIRECTORY_UNAVAILABLE(
-            5017, "Error_DirectoryUnavailable", null, ErrorFlag.ForceLogout ),
+            5017, "Error_DirectoryUnavailable", null ),
     ERROR_ACTIVATION_VALIDATIONFAIL(
             5018, "Error_ActivationValidationFailed", Collections.emptySet() ),
     ERROR_SERVICE_NOT_AVAILABLE(
@@ -184,7 +184,7 @@ public enum PwmError
     ERROR_INVALID_CONFIG(
             5033, "Error_InvalidConfig", Collections.emptySet() ),
     ERROR_INVALID_FORMID(
-            5034, "Error_InvalidFormID", Collections.emptySet() ),
+            5034, "Error_InvalidFormID", Collections.emptySet(), ErrorFlag.Trivial ),
     ERROR_INCORRECT_REQ_SEQUENCE(
             5035, "Error_IncorrectRequestSequence", Collections.emptySet() ),
     ERROR_TOKEN_MISSING_CONTACT(
@@ -310,6 +310,8 @@ public enum PwmError
             6000, "Error_RemoteErrorValue", Collections.emptySet(), ErrorFlag.Permanent ),
     ERROR_TELEMETRY_SEND_ERROR(
             6001, "Error_TelemetrySendError", Collections.emptySet() ),
+    ERROR_HTTP_CLIENT(
+            6002, "Error_HttpError", Collections.emptySet() ),
 
     ERROR_FIELD_REQUIRED(
             5100, "Error_FieldRequired", Collections.emptySet() ),
@@ -351,17 +353,17 @@ public enum PwmError
 
     /* End of list*/;
 
-    enum ErrorFlag
+    private enum ErrorFlag
     {
         Permanent,
-        ForceLogout,
+        Trivial,
     }
 
     private final int errorCode;
     private final String resourceKey;
     private final Set<ChaiError> chaiErrorCode;
     private final boolean errorIsPermanent;
-    private final boolean forceLogout;
+    private final boolean trivial;
 
     PwmError(
             final int errorCode,
@@ -373,9 +375,8 @@ public enum PwmError
         this.resourceKey = resourceKey;
         this.errorCode = errorCode;
         this.errorIsPermanent = JavaHelper.enumArrayContainsValue( errorFlags, ErrorFlag.Permanent );
-        this.forceLogout = JavaHelper.enumArrayContainsValue( errorFlags, ErrorFlag.ForceLogout );
+        this.trivial = JavaHelper.enumArrayContainsValue( errorFlags, ErrorFlag.Trivial );
         this.chaiErrorCode = chaiErrorCode == null ? Collections.emptySet() : Set.copyOf( chaiErrorCode );
-
     }
 
     public String getLocalizedMessage( final Locale locale, final SettingReader config, final String... fieldValue )
@@ -420,9 +421,9 @@ public enum PwmError
         return null;
     }
 
-    private boolean isForceLogout( )
+    public boolean isTrivial()
     {
-        return forceLogout;
+        return trivial;
     }
 
     public boolean isErrorIsPermanent( )

+ 15 - 12
server/src/main/java/password/pwm/health/ApplianceStatusChecker.java

@@ -24,7 +24,6 @@ import org.apache.commons.io.FileUtils;
 import password.pwm.PwmApplication;
 import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
-import password.pwm.bean.SessionLabel;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -59,14 +58,15 @@ public class ApplianceStatusChecker implements HealthSupplier
     }
 
     @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplier.HealthSupplierRequest request )
     {
-        final Supplier<List<HealthRecord>> supplier = () -> doHealthCheck( pwmApplication );
+        final Supplier<List<HealthRecord>> supplier = () -> doHealthCheck( request );
         return Collections.singletonList( supplier );
     }
 
-    public List<HealthRecord> doHealthCheck( final PwmApplication pwmApplication )
+    private List<HealthRecord> doHealthCheck( final HealthSupplier.HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         final boolean isApplianceAvailable = pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.Appliance );
 
         if ( !isApplianceAvailable )
@@ -76,21 +76,23 @@ public class ApplianceStatusChecker implements HealthSupplier
 
         try
         {
-            return List.copyOf( readApplianceHealthStatus( pwmApplication ) );
+            return List.copyOf( readApplianceHealthStatus( request ) );
         }
         catch ( final Exception e )
         {
-            LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, () -> "error communicating with client " + e.getMessage() );
+            LOGGER.error( request.getSessionLabel(), () -> "error communicating with client " + e.getMessage() );
         }
 
         return Collections.emptyList();
     }
 
-    private List<HealthRecord> readApplianceHealthStatus( final PwmApplication pwmApplication ) throws IOException, PwmUnrecoverableException, PwmOperationalException
+    private List<HealthRecord> readApplianceHealthStatus( final HealthSupplier.HealthSupplierRequest request  )
+            throws PwmUnrecoverableException, PwmOperationalException
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         final List<HealthRecord> healthRecords = new ArrayList<>();
 
-        final String url = figureUrl( pwmApplication );
+        final String url = figureUrl( request );
         final Map<String, String> requestHeaders = Collections.singletonMap( "sspr-authorization-token", getApplianceAccessToken( pwmApplication ) );
 
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
@@ -104,9 +106,9 @@ public class ApplianceStatusChecker implements HealthSupplier
                 .headers( requestHeaders )
                 .build();
 
-        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, SessionLabel.HEALTH_SESSION_LABEL );
+        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, request.getSessionLabel() );
 
-        LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "https response from appliance server request: " + response.getBody() );
+        LOGGER.trace( request.getSessionLabel(), () -> "https response from appliance server request: " + response.getBody() );
 
         final String jsonString = response.getBody();
 
@@ -150,8 +152,9 @@ public class ApplianceStatusChecker implements HealthSupplier
         return "";
     }
 
-    private String figureUrl( final PwmApplication pwmApplication ) throws PwmOperationalException
+    private String figureUrl( final HealthSupplier.HealthSupplierRequest request ) throws PwmOperationalException
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         final String hostnameFile = pwmApplication.getPwmEnvironment().getParameters().get( PwmEnvironment.ApplicationParameter.ApplianceHostnameFile );
         if ( StringUtil.isEmpty( hostnameFile ) )
         {
@@ -164,7 +167,7 @@ public class ApplianceStatusChecker implements HealthSupplier
         final String port = pwmApplication.getPwmEnvironment().getParameters().get( PwmEnvironment.ApplicationParameter.AppliancePort );
 
         final String url = "https://" + hostname + ":" + port + "/sspr/appliance-update-status";
-        LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "calculated appliance host url as: " + url );
+        LOGGER.trace( request.getSessionLabel(), () -> "calculated appliance host url as: " + url );
         return url;
     }
 

+ 3 - 8
server/src/main/java/password/pwm/health/CertificateChecker.java

@@ -23,7 +23,6 @@ package password.pwm.health;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.stored.StoredConfigKey;
@@ -52,14 +51,10 @@ public class CertificateChecker implements HealthSupplier
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( CertificateChecker.class );
 
-    public List<HealthRecord> doHealthCheck( final PwmDomain pwmDomain )
-    {
-        final CertificateCheckJob job = new CertificateCheckJob( pwmDomain.getPwmApplication().getConfig() );
-        return Collections.unmodifiableList( job.get() );
-    }
-
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    @Override
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         return Collections.singletonList( new CertificateCheckJob( pwmApplication.getConfig() ) );
     }
 

+ 243 - 118
server/src/main/java/password/pwm/health/ConfigurationChecker.java

@@ -20,6 +20,7 @@
 
 package password.pwm.health;
 
+import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
@@ -27,6 +28,7 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
@@ -61,11 +63,13 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -74,9 +78,9 @@ public class ConfigurationChecker implements HealthSupplier
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigurationChecker.class );
 
-    private static final List<Class<? extends ConfigHealthCheck>> ALL_CHECKS = List.of(
+    private static final List<Class<? extends ConfigDomainHealthCheck>> DOMAIN_CHECKS = List.of(
             VerifyNewUserPasswordPolicy.class,
-            VerifyBasicConfigs.class,
+            VerifyBasicDomainConfigs.class,
             VerifyPasswordStrengthLevels.class,
             VerifyPasswordPolicyConfigs.class,
             VerifyResponseLdapAttribute.class,
@@ -87,60 +91,70 @@ public class ConfigurationChecker implements HealthSupplier
             VerifyPasswordWaitTimes.class,
             VerifyUserPermissionSettings.class );
 
+    private static final List<Class<? extends ConfigSystemHealthCheck>> SYSTEM_CHECKS = List.of(
+            VerifyBasicSystemConfigs.class,
+            VerifyDbConfiguredIfNeededSystem.class );
+
     @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplier.HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
+        final SessionLabel sessionLabel = request.getSessionLabel();
+
+        if ( pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.HIDE_CONFIGURATION_HEALTH_WARNINGS ) )
+        {
+            return Collections.emptyList();
+        }
+
         final List<Supplier<List<HealthRecord>>> suppliers = new ArrayList<>();
         suppliers.add( () -> checkAppMode( pwmApplication ) );
         for ( final PwmDomain domain : pwmApplication.domains().values() )
         {
-            final Supplier<List<HealthRecord>> supplier = () -> allChecks( domain.getConfig(), PwmConstants.DEFAULT_LOCALE );
-            suppliers.add( supplier );
+            suppliers.add( () -> allDomainChecks( sessionLabel, domain.getConfig(), PwmConstants.DEFAULT_LOCALE ) );
         }
+        suppliers.add( () -> allSystemChecks( sessionLabel, pwmApplication.getConfig(), PwmConstants.DEFAULT_LOCALE ) );
         return suppliers;
     }
 
-    public List<HealthRecord> doHealthCheck( final AppConfig appConfig, final Locale locale )
+    public List<HealthRecord> doHealthCheck( final PwmApplication tempApp, final SessionLabel sessionLabel )
     {
-        final List<HealthRecord> healthRecords = new ArrayList<>();
-        for ( final DomainConfig domain : appConfig.getDomainConfigs().values() )
-        {
-           healthRecords.addAll( allChecks( domain, locale ) );
-        }
-        return Collections.unmodifiableList( healthRecords );
+        final HealthSupplier.HealthSupplierRequest request = new HealthSupplierRequest( tempApp, sessionLabel );
+        return jobs( request ).stream().map( Supplier::get ).flatMap( Collection::stream ).collect( Collectors.toList() );
     }
 
-    private List<HealthRecord> checkAppMode( final PwmApplication pwmApplication )
+    private List<HealthRecord> allDomainChecks( final SessionLabel sessionLabel, final DomainConfig config, final Locale locale )
     {
         final List<HealthRecord> records = new ArrayList<>();
-
-        if ( pwmApplication.getApplicationMode() == PwmApplicationMode.CONFIGURATION )
+        for ( final Class<? extends ConfigDomainHealthCheck> clazz : DOMAIN_CHECKS )
         {
-            records.add( HealthRecord.forMessage(
-                    DomainID.systemId(),
-                    HealthMessage.Config_ConfigMode ) );
+            final ConfigDomainHealthCheck healthCheckClass;
+            try
+            {
+                healthCheckClass = clazz.getDeclaredConstructor().newInstance();
+                records.addAll( healthCheckClass.healthCheck( new DomainHealthCheckRequest( config, locale, sessionLabel ) ) );
+            }
+            catch ( final Exception e )
+            {
+                LOGGER.error( () -> "unexpected error during health check operation for class " + clazz.toString() + ", error:" + e.getMessage(), e );
+            }
         }
         return Collections.unmodifiableList( records );
     }
 
-    private List<HealthRecord> allChecks(
-            final DomainConfig config,
+    private List<HealthRecord> allSystemChecks(
+            final SessionLabel sessionLabel,
+            final AppConfig config,
             final Locale locale
     )
     {
-        if ( config.readSettingAsBoolean( PwmSetting.HIDE_CONFIGURATION_HEALTH_WARNINGS ) )
-        {
-            return Collections.emptyList();
-        }
-
         final List<HealthRecord> records = new ArrayList<>();
-        for ( final Class<? extends ConfigHealthCheck> clazz : ALL_CHECKS )
+        for ( final Class<? extends ConfigSystemHealthCheck> clazz : SYSTEM_CHECKS )
         {
-            final ConfigHealthCheck healthCheckClass;
+            final ConfigSystemHealthCheck healthCheckClass;
             try
             {
                 healthCheckClass = clazz.getDeclaredConstructor().newInstance();
-                records.addAll( healthCheckClass.healthCheck( config, locale ) );
+                records.addAll( healthCheckClass.healthCheck( new SystemHealthCheckRequest( config, locale, sessionLabel ) ) );
             }
             catch ( final Exception e )
             {
@@ -150,32 +164,43 @@ public class ConfigurationChecker implements HealthSupplier
         return Collections.unmodifiableList( records );
     }
 
-    static class VerifyNewUserPasswordPolicy implements ConfigHealthCheck
+    private List<HealthRecord> checkAppMode( final PwmApplication pwmApplication )
+    {
+        final List<HealthRecord> records = new ArrayList<>();
+
+        if ( pwmApplication.getApplicationMode() == PwmApplicationMode.CONFIGURATION )
+        {
+            records.add( HealthRecord.forMessage(
+                    DomainID.systemId(),
+                    HealthMessage.Config_ConfigMode ) );
+        }
+        return Collections.unmodifiableList( records );
+    }
+
+    static class VerifyNewUserPasswordPolicy implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig domainConfig, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
             final List<HealthRecord> records = new ArrayList<>();
 
-            if ( domainConfig.readSettingAsBoolean( PwmSetting.NEWUSER_ENABLE ) )
+            if ( config.readSettingAsBoolean( PwmSetting.NEWUSER_ENABLE ) )
             {
-
-
-
-                for ( final NewUserProfile newUserProfile : domainConfig.getNewUserProfiles().values() )
+                for ( final NewUserProfile newUserProfile : config.getNewUserProfiles().values() )
                 {
                     try
                     {
                         final PwmApplication tempApplication = PwmApplication.createPwmApplication( PwmEnvironment.builder().build()
-                                .makeRuntimeInstance( domainConfig.getAppConfig() ) );
+                                .makeRuntimeInstance( config.getAppConfig() ) );
                         final PwmDomain tempDomain = tempApplication.domains().get( ConfigGuideForm.DOMAIN_ID );
 
-                        newUserProfile.getNewUserPasswordPolicy( tempDomain, PwmConstants.DEFAULT_LOCALE );
+                        newUserProfile.getNewUserPasswordPolicy( domainHealthCheckRequest.getSessionLabel(), tempDomain, PwmConstants.DEFAULT_LOCALE );
                     }
                     catch ( final PwmUnrecoverableException e )
                     {
                         records.add( HealthRecord.forMessage(
-                                domainConfig.getDomainID(),
+                                config.getDomainID(),
                                 HealthMessage.NewUser_PwTemplateBad,
                                 PwmSetting.NEWUSER_PASSWORD_POLICY_USER.toMenuLocationDebug( newUserProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
                                 e.getMessage() ) );
@@ -184,20 +209,50 @@ public class ConfigurationChecker implements HealthSupplier
             }
 
             return Collections.unmodifiableList( records );
-
         }
     }
 
+    static class VerifyBasicSystemConfigs implements ConfigSystemHealthCheck
+    {
+        @Override
+        public List<HealthRecord> healthCheck( final SystemHealthCheckRequest systemHealthCheckRequest )
+        {
+            final AppConfig config = systemHealthCheckRequest.getDomainConfig();
+            final Locale locale = systemHealthCheckRequest.getLocale();
+
+            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
+            final List<HealthRecord> records = new ArrayList<>();
+
+            if ( Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) ) )
+            {
+                final String appPropertyKey = "AppProperty" + separator + AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey();
+                records.add( HealthRecord.forMessage(
+                        DomainID.systemId(),
+                        HealthMessage.Config_PromiscuousLDAP,
+                        appPropertyKey ) );
+            }
 
+            if ( config.readSettingAsBoolean( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS ) )
+            {
+                records.add( HealthRecord.forMessage(
+                        DomainID.systemId(),
+                        HealthMessage.Config_ShowDetailedErrors,
+                        PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) );
+            }
+            return Collections.unmodifiableList( records );
+        }
+    }
 
-    static class VerifyBasicConfigs implements ConfigHealthCheck
+    static class VerifyBasicDomainConfigs implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
             final String siteUrl = config.getAppConfig().readSettingAsString( PwmSetting.PWM_SITE_URL );
-            final String separator = LocaleHelper.getLocalizedMessage( locale, Config.Display_SettingNavigationSeparator, null );
 
             if ( siteUrl == null || siteUrl.isEmpty() || siteUrl.equals(
                     PwmSetting.PWM_SITE_URL.getDefaultValue( config.getTemplate() ).toNativeObject() ) )
@@ -216,23 +271,6 @@ public class ConfigurationChecker implements HealthSupplier
                         PwmSetting.LDAP_ENABLE_WIRE_TRACE.toMenuLocationDebug( null, locale ) ) );
             }
 
-            if ( Boolean.parseBoolean( config.readAppProperty( AppProperty.LDAP_PROMISCUOUS_ENABLE ) ) )
-            {
-                final String appPropertyKey = "AppProperty" + separator + AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey();
-                records.add( HealthRecord.forMessage(
-                        config.getDomainID(),
-                        HealthMessage.Config_PromiscuousLDAP,
-                        appPropertyKey ) );
-            }
-
-            if ( config.getAppConfig().readSettingAsBoolean( PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS ) )
-            {
-                records.add( HealthRecord.forMessage(
-                        config.getDomainID(),
-                        HealthMessage.Config_ShowDetailedErrors,
-                        PwmSetting.DISPLAY_SHOW_DETAILED_ERRORS.toMenuLocationDebug( null, locale ) ) );
-            }
-
             if ( config.getLdapProfiles().isEmpty() )
             {
                 records.add( HealthRecord.forMessage(
@@ -291,11 +329,12 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyPasswordStrengthLevels implements ConfigHealthCheck
+    static class VerifyPasswordStrengthLevels implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
             final List<HealthRecord> records = new ArrayList<>();
 
             final List<StoredConfigKey> interestedKeys = CollectionUtil.iteratorToStream( config.getStoredConfiguration().keys() )
@@ -303,62 +342,71 @@ public class ConfigurationChecker implements HealthSupplier
                     .filter( key -> key.toPwmSetting().getSyntax() == PwmSettingSyntax.PASSWORD )
                     .collect( Collectors.toList() );
 
-            try
+            for ( final StoredConfigKey key : interestedKeys )
             {
-                for ( final StoredConfigKey key : interestedKeys )
+                try
                 {
-                    final PwmSetting pwmSetting = key.toPwmSetting();
-                    final StoredValue storedValue = config.getStoredConfiguration().readStoredValue( key ).orElseThrow();
-                    final PasswordData passwordValue = ValueTypeConverter.valueToPassword( storedValue );
-                    if ( passwordValue != null )
-                    {
-                        final String stringValue = passwordValue.getStringValue();
-
-                        if ( StringUtil.notEmpty( stringValue ) )
-                        {
-                            final int strength = PasswordUtility.judgePasswordStrength( config, stringValue );
-                            if ( strength < 50 )
-                            {
-                                records.add( HealthRecord.forMessage(
-                                        config.getDomainID(),
-                                        HealthMessage.Config_WeakPassword,
-                                        pwmSetting.toMenuLocationDebug( key.getProfileID(), locale ), String.valueOf( strength ) ) );
-                            }
-                        }
-                    }
+                    checkKey( domainHealthCheckRequest, key ).ifPresent( records::add );
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    LOGGER.error( () -> "unexpected error examining password strength of configuration: " );
                 }
             }
-            catch ( final PwmUnrecoverableException e )
+
+            return Collections.unmodifiableList( records );
+        }
+
+        private Optional<HealthRecord> checkKey( final DomainHealthCheckRequest domainHealthCheckRequest, final StoredConfigKey key )
+                throws PwmUnrecoverableException
+        {
+            final StoredConfiguration config = domainHealthCheckRequest.getDomainConfig().getStoredConfiguration();
+            final PwmSetting pwmSetting = key.toPwmSetting();
+            final StoredValue storedValue = config.readStoredValue( key ).orElseThrow();
+            final PasswordData passwordValue = ValueTypeConverter.valueToPassword( storedValue );
+
+            if ( passwordValue != null )
             {
-                LOGGER.error( () -> "unexpected error examining password strength of configuration: " );
+                final String stringValue = passwordValue.getStringValue();
+
+                if ( StringUtil.notEmpty( stringValue ) )
+                {
+                    final int strength = PasswordUtility.judgePasswordStrength( domainHealthCheckRequest.getDomainConfig(), stringValue );
+                    if ( strength < 50 )
+                    {
+                        return Optional.of( HealthRecord.forMessage(
+                                domainHealthCheckRequest.getDomainConfig().getDomainID(),
+                                HealthMessage.Config_WeakPassword,
+                                pwmSetting.toMenuLocationDebug( key.getProfileID(), domainHealthCheckRequest.getLocale() ), String.valueOf( strength ) ) );
+                    }
+                }
             }
 
-            return Collections.unmodifiableList( records );
+            return Optional.empty();
         }
     }
 
-    static class VerifyResponseLdapAttribute implements ConfigHealthCheck
+    static class VerifyResponseLdapAttribute implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck(
-                final DomainConfig config,
-                final Locale locale
-        )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
-            final PwmSetting[] interestedSettings = new PwmSetting[]
-                    {
+            final List<PwmSetting> interestedSettings = List.of(
                             PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE,
-                            PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE,
-                    };
+                            PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE );
+
             for ( final PwmSetting loopSetting : interestedSettings )
             {
-                if ( config.getResponseStorageLocations( loopSetting ).contains( DataStorageMethod.LDAP ) )
+                if ( config.readGenericStorageLocations( loopSetting ).contains( DataStorageMethod.LDAP ) )
                 {
                     for ( final LdapProfile ldapProfile : config.getLdapProfiles().values() )
                     {
                         final String responseAttr = ldapProfile.readSettingAsString( PwmSetting.CHALLENGE_USER_ATTRIBUTE );
-                        final boolean hasResponseAttribute = responseAttr != null && !responseAttr.isEmpty();
+                        final boolean hasResponseAttribute = StringUtil.notEmpty( responseAttr );
                         if ( !hasResponseAttribute )
                         {
                             records.add( HealthRecord.forMessage(
@@ -375,11 +423,14 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyDbConfiguredIfNeeded implements ConfigHealthCheck
+    static class VerifyDbConfiguredIfNeeded implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
             if ( !config.getAppConfig().hasDbConfigured() )
             {
@@ -388,12 +439,11 @@ public class ConfigurationChecker implements HealthSupplier
                     final List<PwmSetting> settingsToCheck = List.of(
                             PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE,
                             PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE,
-                            PwmSetting.INTRUDER_STORAGE_METHOD,
                             PwmSetting.EVENTS_USER_STORAGE_METHOD );
 
                     for ( final PwmSetting loopSetting : settingsToCheck )
                     {
-                        if ( config.getResponseStorageLocations( loopSetting ).contains( DataStorageMethod.DB ) )
+                        if ( config.readGenericStorageLocations( loopSetting ).contains( DataStorageMethod.DB ) )
                         {
                             causalSettings.add( loopSetting );
                         }
@@ -414,7 +464,7 @@ public class ConfigurationChecker implements HealthSupplier
                 }
             }
 
-            if ( config.getResponseStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE ).contains( DataStorageMethod.LOCALDB ) )
+            if ( config.readGenericStorageLocations( PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE ).contains( DataStorageMethod.LOCALDB ) )
             {
                 records.add( HealthRecord.forMessage(
                         config.getDomainID(),
@@ -422,7 +472,7 @@ public class ConfigurationChecker implements HealthSupplier
                         PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE.toMenuLocationDebug( null, locale ) ) );
             }
 
-            if ( config.getOtpSecretStorageLocations( PwmSetting.OTP_SECRET_WRITE_PREFERENCE ).contains( DataStorageMethod.LOCALDB ) )
+            if ( config.readGenericStorageLocations( PwmSetting.OTP_SECRET_WRITE_PREFERENCE ).contains( DataStorageMethod.LOCALDB ) )
             {
                 records.add( HealthRecord.forMessage(
                         config.getDomainID(),
@@ -434,11 +484,52 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyPasswordPolicyConfigs implements ConfigHealthCheck
+    static class VerifyDbConfiguredIfNeededSystem implements ConfigSystemHealthCheck
+    {
+        @Override
+        public List<HealthRecord> healthCheck( final SystemHealthCheckRequest systemHealthCheckRequest )
+        {
+            final AppConfig config = systemHealthCheckRequest.getDomainConfig();
+            final Locale locale = systemHealthCheckRequest.getLocale();
+            final List<HealthRecord> records = new ArrayList<>();
+            if ( !config.hasDbConfigured() )
+            {
+                final Set<PwmSetting> causalSettings = new LinkedHashSet<>();
+                {
+                    final List<PwmSetting> settingsToCheck = List.of(
+                            PwmSetting.INTRUDER_STORAGE_METHOD  );
+
+                    for ( final PwmSetting loopSetting : settingsToCheck )
+                    {
+                        if ( config.readGenericStorageLocations( loopSetting ).contains( DataStorageMethod.DB ) )
+                        {
+                            causalSettings.add( loopSetting );
+                        }
+                    }
+                }
+
+
+                for ( final PwmSetting setting : causalSettings )
+                {
+                    records.add( HealthRecord.forMessage(
+                            DomainID.systemId(),
+                            HealthMessage.Config_MissingDB,
+                            setting.toMenuLocationDebug( null, locale ) ) );
+                }
+            }
+
+            return records;
+        }
+    }
+
+    static class VerifyPasswordPolicyConfigs implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
             for ( final String profileID : config.getPasswordProfileIDs() )
             {
@@ -456,11 +547,14 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyNewUserLdapProfile implements ConfigHealthCheck
+    static class VerifyNewUserLdapProfile implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
             for ( final NewUserProfile newUserProfile : config.getNewUserProfiles().values() )
             {
@@ -482,11 +576,14 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyIfDeprecatedJsFormOptionUsed implements ConfigHealthCheck
+    static class VerifyIfDeprecatedJsFormOptionUsed implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
 
             final List<StoredConfigKey> interestedKeys = CollectionUtil.iteratorToStream( config.getStoredConfiguration().keys() )
@@ -517,11 +614,14 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyIfDeprecatedSendMethodValuesUsed implements ConfigHealthCheck
+    static class VerifyIfDeprecatedSendMethodValuesUsed implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final Set<MessageSendMethod> deprecatedMethods = Arrays
                     .stream( MessageSendMethod.values() )
                     .filter( MessageSendMethod::isDeprecated )
@@ -605,11 +705,14 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    static class VerifyPasswordWaitTimes implements ConfigHealthCheck
+    static class VerifyPasswordWaitTimes implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
 
             for ( final ChangePasswordProfile changePasswordProfile : config.getChangePasswordProfile().values() )
@@ -637,11 +740,14 @@ public class ConfigurationChecker implements HealthSupplier
 
 
 
-    static class VerifyUserPermissionSettings implements ConfigHealthCheck
+    static class VerifyUserPermissionSettings implements ConfigDomainHealthCheck
     {
         @Override
-        public List<HealthRecord> healthCheck( final DomainConfig config, final Locale locale )
+        public List<HealthRecord> healthCheck( final DomainHealthCheckRequest domainHealthCheckRequest )
         {
+            final DomainConfig config = domainHealthCheckRequest.getDomainConfig();
+            final Locale locale = domainHealthCheckRequest.getLocale();
+
             final List<HealthRecord> records = new ArrayList<>();
             final StoredConfiguration storedConfiguration = config.getStoredConfiguration();
             final List<StoredConfigKey> interestedKeys = CollectionUtil.iteratorToStream( config.getStoredConfiguration().keys() )
@@ -713,10 +819,29 @@ public class ConfigurationChecker implements HealthSupplier
         }
     }
 
-    interface ConfigHealthCheck
+    @Value
+    static class DomainHealthCheckRequest
+    {
+        private final DomainConfig domainConfig;
+        private final Locale locale;
+        private final SessionLabel sessionLabel;
+    }
+
+    interface ConfigDomainHealthCheck
+    {
+        List<HealthRecord> healthCheck( DomainHealthCheckRequest domainHealthCheckRequest );
+    }
+
+    @Value
+    static class SystemHealthCheckRequest
+    {
+        private final AppConfig domainConfig;
+        private final Locale locale;
+        private final SessionLabel sessionLabel;
+    }
+
+    interface ConfigSystemHealthCheck
     {
-        List<HealthRecord> healthCheck(
-                DomainConfig domainConfig,
-                Locale locale );
+        List<HealthRecord> healthCheck( SystemHealthCheckRequest systemHealthCheckRequest );
     }
 }

+ 4 - 3
server/src/main/java/password/pwm/health/DatabaseStatusChecker.java

@@ -25,8 +25,8 @@ import password.pwm.PwmEnvironment;
 import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
 import password.pwm.error.PwmException;
-import password.pwm.util.db.DatabaseAccessor;
-import password.pwm.util.db.DatabaseTable;
+import password.pwm.svc.db.DatabaseAccessor;
+import password.pwm.svc.db.DatabaseTable;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
@@ -38,8 +38,9 @@ public class DatabaseStatusChecker implements HealthSupplier
     private static final PwmLogger LOGGER = PwmLogger.forClass( DatabaseStatusChecker.class );
 
     @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         final Supplier<List<HealthRecord>> supplier = () -> doHealthCheck( pwmApplication );
         return Collections.singletonList( supplier );
     }

+ 5 - 4
server/src/main/java/password/pwm/health/HealthRecord.java

@@ -25,7 +25,8 @@ import org.jetbrains.annotations.NotNull;
 import password.pwm.bean.DomainID;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.SettingReader;
-import password.pwm.ws.server.rest.bean.HealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthRecord;
 
 import java.io.Serializable;
 import java.time.Instant;
@@ -138,15 +139,15 @@ public class HealthRecord implements Serializable, Comparable<HealthRecord>
         return Collections.singletonList( this );
     }
 
-    public static HealthData asHealthDataBean(
+    public static PublicHealthData asHealthDataBean(
             final DomainConfig domainConfig,
             final Locale locale,
             final List<HealthRecord> profileRecords
     )
     {
-        final List<password.pwm.ws.server.rest.bean.HealthRecord> healthRecordBeans = password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords(
+        final List<PublicHealthRecord> healthRecordBeans = PublicHealthRecord.fromHealthRecords(
                 profileRecords, locale, domainConfig );
-        return HealthData.builder()
+        return PublicHealthData.builder()
                 .timestamp( Instant.now() )
                 .overall( HealthUtils.getMostSevereHealthStatus( profileRecords ).toString() )
                 .records( healthRecordBeans )

+ 40 - 44
server/src/main/java/password/pwm/health/HealthService.java

@@ -27,9 +27,10 @@ import password.pwm.bean.DomainID;
 import password.pwm.bean.SessionLabel;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.debug.DebugItemGenerator;
+import password.pwm.svc.AbstractPwmService;
 import password.pwm.svc.PwmService;
 import password.pwm.util.PwmScheduler;
+import password.pwm.util.debug.DebugItemGenerator;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
@@ -51,7 +52,6 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
@@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 import java.util.zip.ZipOutputStream;
 
-public class HealthService implements PwmService
+public class HealthService extends AbstractPwmService implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( HealthService.class );
 
@@ -81,8 +81,6 @@ public class HealthService implements PwmService
     private final Map<HealthMonitorFlag, Serializable> healthProperties = new ConcurrentHashMap<>();
     private final AtomicInteger healthCheckCount = new AtomicInteger( 0 );
 
-    private STATUS status = STATUS.CLOSED;
-    private PwmApplication pwmApplication;
     private volatile HealthData healthData = emptyHealthData();
 
     enum HealthMonitorFlag
@@ -96,18 +94,16 @@ public class HealthService implements PwmService
     }
 
     @Override
-    public void init( final PwmApplication pwmApplication, final DomainID domainID )
+    public STATUS postAbstractInit( final PwmApplication pwmApplication, final DomainID domainID )
             throws PwmException
     {
-        this.pwmApplication = Objects.requireNonNull( pwmApplication );
         this.healthData = emptyHealthData();
         settings = HealthMonitorSettings.fromConfiguration( pwmApplication.getConfig() );
 
         if ( !settings.isHealthCheckEnabled() )
         {
             LOGGER.debug( () -> "health monitor will remain inactive due to AppProperty " + AppProperty.HEALTHCHECK_ENABLED.getKey() );
-            status = STATUS.CLOSED;
-            return;
+            return STATUS.CLOSED;
         }
 
         executorService = PwmScheduler.makeBackgroundExecutor( pwmApplication, this.getClass() );
@@ -119,12 +115,12 @@ public class HealthService implements PwmService
             pwmApplication.getPwmScheduler().scheduleFixedRateJob( new ThreadDumpLogger(), executorService, TimeDuration.SECOND, settings.getThreadDumpInterval() );
         }
 
-        status = STATUS.OPEN;
+        return STATUS.OPEN;
     }
 
     public Instant getLastHealthCheckTime( )
     {
-        if ( status != STATUS.OPEN )
+        if ( status() != STATUS.OPEN )
         {
             return null;
         }
@@ -134,22 +130,17 @@ public class HealthService implements PwmService
 
     public HealthStatus getMostSevereHealthStatus( )
     {
-        if ( status != STATUS.OPEN )
+        if ( status() != STATUS.OPEN )
         {
             return HealthStatus.GOOD;
         }
         return HealthUtils.getMostSevereHealthStatus( getHealthRecords( ) );
     }
 
-    @Override
-    public STATUS status( )
-    {
-        return status;
-    }
 
     public Set<HealthRecord> getHealthRecords( )
     {
-        if ( status != STATUS.OPEN )
+        if ( status() != STATUS.OPEN )
         {
             return Collections.emptySet();
         }
@@ -158,12 +149,12 @@ public class HealthService implements PwmService
         {
             final Instant startTime = Instant.now();
             LOGGER.trace( () ->  "begin force immediate check" );
-            final Future future = pwmApplication.getPwmScheduler().scheduleJob( new ImmediateJob(), executorService, TimeDuration.ZERO );
+            final Future future = getPwmApplication().getPwmScheduler().scheduleJob( new ImmediateJob(), executorService, TimeDuration.ZERO );
             settings.getMaximumForceCheckWait().pause( future::isDone );
             LOGGER.trace( () ->  "exit force immediate check, done=" + future.isDone(), () -> TimeDuration.fromCurrent( startTime ) );
         }
 
-        pwmApplication.getPwmScheduler().scheduleJob( new UpdateJob(), executorService, settings.getNominalCheckInterval() );
+        getPwmApplication().getPwmScheduler().scheduleJob( new UpdateJob(), executorService, settings.getNominalCheckInterval() );
 
         {
             final HealthData localHealthData = this.healthData;
@@ -188,7 +179,7 @@ public class HealthService implements PwmService
             supportZipWriterService.shutdown();
         }
         healthData = emptyHealthData();
-        status = STATUS.CLOSED;
+        setStatus( STATUS.CLOSED );
     }
 
     private HealthData emptyHealthData()
@@ -197,7 +188,7 @@ public class HealthService implements PwmService
     }
 
     @Override
-    public List<HealthRecord> healthCheck( )
+    public List<HealthRecord> serviceHealthCheck( )
     {
         return Collections.emptyList();
     }
@@ -206,7 +197,7 @@ public class HealthService implements PwmService
     private void doHealthChecks( )
     {
         final int counter = healthCheckCount.getAndIncrement();
-        if ( status != STATUS.OPEN )
+        if ( status() != STATUS.OPEN )
         {
             return;
         }
@@ -215,8 +206,7 @@ public class HealthService implements PwmService
         LOGGER.trace( () -> "beginning health check execution #" + counter  );
         final List<HealthRecord> tempResults = new ArrayList<>();
 
-
-        for ( final Supplier<List<HealthRecord>> loopSupplier : gatherSuppliers( pwmApplication ) )
+        for ( final Supplier<List<HealthRecord>> loopSupplier : gatherSuppliers( getPwmApplication(), getSessionLabel() ) )
         {
             try
             {
@@ -228,7 +218,7 @@ public class HealthService implements PwmService
             }
             catch ( final Exception e )
             {
-                if ( status == STATUS.OPEN )
+                if ( status() == STATUS.OPEN )
                 {
                     LOGGER.warn( () -> "unexpected error during healthCheck: " + e.getMessage(), e );
                 }
@@ -239,30 +229,36 @@ public class HealthService implements PwmService
         LOGGER.trace( () -> "completed health check execution #" + counter, () -> TimeDuration.fromCurrent( startTime ) );
     }
 
-    private static List<Supplier<List<HealthRecord>>> gatherSuppliers( final PwmApplication pwmApplication )
+    private static List<Supplier<List<HealthRecord>>> gatherSuppliers(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel
+    )
     {
         final List<Supplier<List<HealthRecord>>> suppliers = new ArrayList<>();
 
-        for ( final PwmService service : pwmApplication.getAppAndDomainPwmServices() )
+        for ( final Map.Entry<DomainID, List<PwmService>> domainIDListEntry : pwmApplication.getAppAndDomainPwmServices().entrySet() )
         {
-            try
+            for ( final PwmService service : domainIDListEntry.getValue() )
             {
-                final List<HealthRecord> loopResults = service.healthCheck();
-                if ( loopResults != null )
+                try
                 {
-                    final Supplier<List<HealthRecord>> wrappedSupplier = () -> loopResults;
-                    suppliers.add( wrappedSupplier );
+                    final List<HealthRecord> loopResults = service.healthCheck();
+                    if ( loopResults != null )
+                    {
+                        final Supplier<List<HealthRecord>> wrappedSupplier = () -> loopResults;
+                        suppliers.add( wrappedSupplier );
+                    }
+                }
+                catch ( final Exception e )
+                {
+                    LOGGER.warn( () -> "unexpected error during healthCheck: " + e.getMessage(), e );
                 }
-            }
-            catch ( final Exception e )
-            {
-                LOGGER.warn( () -> "unexpected error during healthCheck: " + e.getMessage(), e );
             }
         }
 
         for ( final HealthSupplier supplier : HEALTH_SUPPLIERS )
         {
-            suppliers.addAll( supplier.jobs( pwmApplication ) );
+            suppliers.addAll( supplier.jobs( new HealthSupplier.HealthSupplierRequest( pwmApplication, sessionLabel ) ) );
         }
 
         return Collections.unmodifiableList( suppliers );
@@ -328,11 +324,11 @@ public class HealthService implements PwmService
 
     private void scheduleNextZipOutput()
     {
-        final int intervalSeconds = JavaHelper.silentParseInt( pwmApplication.getConfig().readAppProperty( AppProperty.HEALTH_SUPPORT_BUNDLE_WRITE_INTERVAL_SECONDS ), 0 );
+        final int intervalSeconds = JavaHelper.silentParseInt( getPwmApplication().getConfig().readAppProperty( AppProperty.HEALTH_SUPPORT_BUNDLE_WRITE_INTERVAL_SECONDS ), 0 );
         if ( intervalSeconds > 0 )
         {
             final TimeDuration intervalDuration = TimeDuration.of( intervalSeconds, TimeDuration.Unit.SECONDS );
-            pwmApplication.getPwmScheduler().scheduleJob( new SupportZipFileWriter( pwmApplication ), supportZipWriterService, intervalDuration );
+            getPwmApplication().getPwmScheduler().scheduleJob( new SupportZipFileWriter( getPwmApplication() ), supportZipWriterService, intervalDuration );
         }
     }
 
@@ -354,7 +350,7 @@ public class HealthService implements PwmService
             }
             catch ( final Exception e )
             {
-                LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "error writing support zip to file system: " + e.getMessage() );
+                LOGGER.debug( getSessionLabel(), () -> "error writing support zip to file system: " + e.getMessage() );
             }
 
             scheduleNextZipOutput();
@@ -363,14 +359,14 @@ public class HealthService implements PwmService
         private void writeSupportZipToAppPath()
                 throws IOException, PwmUnrecoverableException
         {
-            final File appPath = HealthService.this.pwmApplication.getPwmEnvironment().getApplicationPath();
+            final File appPath = getPwmApplication().getPwmEnvironment().getApplicationPath();
             if ( !appPath.exists() )
             {
                 return;
             }
 
             final int rotationCount = JavaHelper.silentParseInt( pwmApplication.getConfig().readAppProperty( AppProperty.HEALTH_SUPPORT_BUNDLE_FILE_WRITE_COUNT ), 10 );
-            final DebugItemGenerator debugItemGenerator = new DebugItemGenerator( pwmApplication, SessionLabel.HEALTH_SESSION_LABEL );
+            final DebugItemGenerator debugItemGenerator = new DebugItemGenerator( pwmApplication, getSessionLabel() );
 
             final File supportPath = new File( appPath.getPath() + File.separator + "support" );
 
@@ -385,7 +381,7 @@ public class HealthService implements PwmService
 
             try ( ZipOutputStream zipOutputStream = new ZipOutputStream( new FileOutputStream( newSupportFile ) ) )
             {
-                LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "beginning periodic support bundle filesystem output" );
+                LOGGER.trace( getSessionLabel(), () -> "beginning periodic support bundle filesystem output" );
                 debugItemGenerator.outputZipDebugFile( zipOutputStream );
             }
 

+ 10 - 1
server/src/main/java/password/pwm/health/HealthSupplier.java

@@ -20,12 +20,21 @@
 
 package password.pwm.health;
 
+import lombok.Value;
 import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
 
 import java.util.List;
 import java.util.function.Supplier;
 
 public interface HealthSupplier
 {
-    List<Supplier<List<HealthRecord>>> jobs( PwmApplication pwmApplication );
+    List<Supplier<List<HealthRecord>>> jobs( HealthSupplierRequest request );
+
+    @Value
+    class HealthSupplierRequest
+    {
+        private final PwmApplication pwmApplication;
+        private final SessionLabel sessionLabel;
+    }
 }

+ 3 - 1
server/src/main/java/password/pwm/health/JavaChecker.java

@@ -32,8 +32,10 @@ import java.util.function.Supplier;
 public class JavaChecker implements HealthSupplier
 {
     @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplier.HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
+
         final Supplier<List<HealthRecord>> supplier = () -> doHealthCheck( pwmApplication );
         return Collections.singletonList( supplier );
     }

+ 69 - 55
server/src/main/java/password/pwm/health/LDAPHealthChecker.java

@@ -64,7 +64,7 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.util.password.RandomPasswordGenerator;
-import password.pwm.ws.server.rest.bean.HealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthData;
 
 import java.io.Serializable;
 import java.net.InetAddress;
@@ -89,15 +89,15 @@ public class LDAPHealthChecker implements HealthSupplier
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LDAPHealthChecker.class );
 
-    @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplier.HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         return pwmApplication.domains().values().stream()
-                .map( domain -> ( Supplier<List<HealthRecord>> ) () -> doHealthCheck( domain ) )
+                .map( domain -> ( Supplier<List<HealthRecord>> ) () -> doHealthCheck( request.getSessionLabel(), domain ) )
                 .collect( Collectors.toList() );
     }
 
-    public List<HealthRecord> doHealthCheck( final PwmDomain pwmDomain )
+    public List<HealthRecord> doHealthCheck( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
         final DomainConfig config = pwmDomain.getConfig();
         final List<HealthRecord> returnRecords = new ArrayList<>();
@@ -107,18 +107,18 @@ public class LDAPHealthChecker implements HealthSupplier
         {
             final String profileID = entry.getKey();
             final List<HealthRecord> profileRecords = new ArrayList<>(
-                    checkBasicLdapConnectivity( pwmDomain, config, entry.getValue(), true )
+                    checkBasicLdapConnectivity( sessionLabel, pwmDomain, config, entry.getValue(), true )
             );
 
             if ( profileRecords.isEmpty() )
             {
-                profileRecords.addAll( checkLdapServerUrls( pwmDomain, config, ldapProfiles.get( profileID ) ) );
+                profileRecords.addAll( checkLdapServerUrls( sessionLabel, pwmDomain, config, ldapProfiles.get( profileID ) ) );
             }
 
             if ( profileRecords.isEmpty() )
             {
                 profileRecords.add( HealthRecord.forMessage( pwmDomain.getDomainID(), HealthMessage.LDAP_OK ) );
-                profileRecords.addAll( doLdapTestUserCheck( config, ldapProfiles.get( profileID ), pwmDomain ) );
+                profileRecords.addAll( doLdapTestUserCheck( sessionLabel, config, ldapProfiles.get( profileID ), pwmDomain ) );
             }
             returnRecords.addAll( profileRecords );
         }
@@ -154,13 +154,13 @@ public class LDAPHealthChecker implements HealthSupplier
             final List<String> urls = config.getLdapProfiles().values().iterator().next().readSettingAsStringArray( PwmSetting.LDAP_SERVER_URLS );
             if ( urls != null && !urls.isEmpty() && StringUtil.notEmpty( urls.iterator().next() ) )
             {
-                returnRecords.addAll( checkVendorSameness( pwmDomain ) );
+                returnRecords.addAll( checkVendorSameness( sessionLabel, pwmDomain ) );
 
-                returnRecords.addAll( checkUserPermissionValues( pwmDomain ) );
+                returnRecords.addAll( checkUserPermissionValues( sessionLabel, pwmDomain ) );
 
-                returnRecords.addAll( checkLdapDNSyntaxValues( pwmDomain ) );
+                returnRecords.addAll( checkLdapDNSyntaxValues( sessionLabel, pwmDomain ) );
 
-                returnRecords.addAll( checkNewUserPasswordTemplateSetting( pwmDomain, config ) );
+                returnRecords.addAll( checkNewUserPasswordTemplateSetting( sessionLabel, pwmDomain, config ) );
 
      //           returnRecords.addAll( checkUserSearching( pwmApplication ) );
             }
@@ -171,6 +171,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
     @SuppressWarnings( "checkstyle:MethodLength" )
     public List<HealthRecord> doLdapTestUserCheck(
+            final SessionLabel sessionLabel,
             final DomainConfig config,
             final LdapProfile ldapProfile,
             final PwmDomain pwmDomain
@@ -189,13 +190,13 @@ public class LDAPHealthChecker implements HealthSupplier
 
         try
         {
-            testUserDN = ldapProfile.readCanonicalDN( pwmDomain, testUserDN );
-            proxyUserDN = ldapProfile.readCanonicalDN( pwmDomain, proxyUserDN );
+            testUserDN = ldapProfile.readCanonicalDN( sessionLabel, pwmDomain, testUserDN );
+            proxyUserDN = ldapProfile.readCanonicalDN( sessionLabel, pwmDomain, proxyUserDN );
         }
         catch ( final PwmUnrecoverableException e )
         {
             final String msgString = e.getMessage();
-            LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "unexpected error while testing test user (during object creation): message="
+            LOGGER.trace( sessionLabel, () -> "unexpected error while testing test user (during object creation): message="
                     + msgString + " debug info: " + JavaHelper.readHostileExceptionMessage( e ) );
             returnRecords.add( HealthRecord.forMessage( pwmDomain.getDomainID(), HealthMessage.LDAP_TestUserUnexpected,
                     PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE ),
@@ -225,7 +226,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
                 chaiProvider = LdapOperationsHelper.createChaiProvider(
                         pwmDomain,
-                        SessionLabel.HEALTH_SESSION_LABEL,
+                        sessionLabel,
                         ldapProfile,
                         config,
                         proxyUserDN,
@@ -249,7 +250,7 @@ public class LDAPHealthChecker implements HealthSupplier
             {
                 final String msgString = e.getMessage();
                 LOGGER.trace(
-                        SessionLabel.HEALTH_SESSION_LABEL,
+                        sessionLabel,
                         () -> "unexpected error while testing test user (during object creation): message="
                                 + msgString + " debug info: " + JavaHelper.readHostileExceptionMessage( e )
                 );
@@ -278,7 +279,7 @@ public class LDAPHealthChecker implements HealthSupplier
             }
 
             LOGGER.trace(
-                    SessionLabel.HEALTH_SESSION_LABEL,
+                    sessionLabel,
                     () -> "beginning process to check ldap test user password read/write operations for profile "
                             + ldapProfile.getIdentifier()
             );
@@ -295,7 +296,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     }
                     catch ( final Exception e )
                     {
-                        LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "error reading user password from directory " + e.getMessage() );
+                        LOGGER.debug( sessionLabel, () -> "error reading user password from directory " + e.getMessage() );
                         returnRecords.add( HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.LDAP_TestUserReadPwError,
@@ -312,7 +313,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     final UserIdentity userIdentity = UserIdentity.create( testUserDN, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
 
                     final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser(
-                            pwmDomain, null, userIdentity, theUser );
+                            pwmDomain, sessionLabel, userIdentity, theUser );
 
                     boolean doPasswordChange = true;
                     final int minLifetimeSeconds = passwordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
@@ -320,7 +321,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     {
                         final Instant pwdLastModified = PasswordUtility.determinePwdLastModified(
                                 pwmDomain,
-                                SessionLabel.HEALTH_SESSION_LABEL,
+                                sessionLabel,
                                 userIdentity
                         );
 
@@ -329,7 +330,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         {
                             final UserInfo userInfo = UserInfoFactory.newUserInfo(
                                     pwmDomain.getPwmApplication(),
-                                    SessionLabel.HEALTH_SESSION_LABEL,
+                                    sessionLabel,
                                     locale,
                                     userIdentity,
                                     chaiProvider
@@ -340,14 +341,14 @@ public class LDAPHealthChecker implements HealthSupplier
                         {
                             final boolean withinMinLifetime = PasswordUtility.isPasswordWithinMinimumLifetimeImpl(
                                     theUser,
-                                    SessionLabel.HEALTH_SESSION_LABEL,
+                                    sessionLabel,
                                     passwordPolicy,
                                     pwdLastModified,
                                     passwordStatus
                             );
                             if ( withinMinLifetime )
                             {
-                                LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "skipping test user password set due to password being within minimum lifetime" );
+                                LOGGER.trace( sessionLabel, () -> "skipping test user password set due to password being within minimum lifetime" );
                                 doPasswordChange = false;
                             }
                         }
@@ -358,7 +359,7 @@ public class LDAPHealthChecker implements HealthSupplier
                         try
                         {
                             theUser.setPassword( newPassword.getStringValue() );
-                            LOGGER.debug( SessionLabel.HEALTH_SESSION_LABEL, () -> "set random password on test user " + userIdentity.toDisplayString() );
+                            LOGGER.debug( sessionLabel, () -> "set random password on test user " + userIdentity.toDisplayString() );
                         }
                         catch ( final ChaiException e )
                         {
@@ -377,7 +378,7 @@ public class LDAPHealthChecker implements HealthSupplier
             catch ( final Exception e )
             {
                 final String msg = "error setting test user password: " + JavaHelper.readHostileExceptionMessage( e );
-                LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, () -> msg, e );
+                LOGGER.error( sessionLabel, () -> msg, e );
                 returnRecords.add( HealthRecord.forMessage(
                         pwmDomain.getDomainID(),
                         HealthMessage.LDAP_TestUserUnexpected,
@@ -392,7 +393,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 final UserIdentity userIdentity = UserIdentity.create( theUser.getEntryDN(), ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
                 final UserInfo userInfo = UserInfoFactory.newUserInfo(
                         pwmDomain.getPwmApplication(),
-                        SessionLabel.HEALTH_SESSION_LABEL,
+                        sessionLabel,
                         PwmConstants.DEFAULT_LOCALE,
                         userIdentity,
                         chaiProvider
@@ -445,6 +446,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
 
     public List<HealthRecord> checkLdapServerUrls(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final DomainConfig config,
             final LdapProfile ldapProfile
@@ -460,7 +462,7 @@ public class LDAPHealthChecker implements HealthSupplier
             {
                 chaiProvider = LdapOperationsHelper.createChaiProvider(
                         pwmDomain,
-                        SessionLabel.HEALTH_SESSION_LABEL,
+                        sessionLabel,
                         config,
                         ldapProfile,
                         Collections.singletonList( loopURL ),
@@ -498,6 +500,7 @@ public class LDAPHealthChecker implements HealthSupplier
     }
 
     public List<HealthRecord> checkBasicLdapConnectivity(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final DomainConfig config,
             final LdapProfile ldapProfile,
@@ -532,7 +535,7 @@ public class LDAPHealthChecker implements HealthSupplier
                             ldapProfile.getIdentifier(),
                             "Missing Proxy User Password: " + menuLocationStr ) );
                 }
-                chaiProvider = LdapOperationsHelper.createChaiProvider( pwmDomain, SessionLabel.HEALTH_SESSION_LABEL, ldapProfile, config, proxyDN, proxyPW );
+                chaiProvider = LdapOperationsHelper.createChaiProvider( pwmDomain, sessionLabel, ldapProfile, config, proxyDN, proxyPW );
                 final ChaiUser adminEntry = chaiProvider.getEntryFactory().newChaiUser( proxyDN );
                 adminEntry.exists();
                 directoryVendor = chaiProvider.getDirectoryVendor();
@@ -598,7 +601,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
             if ( directoryVendor != null && directoryVendor == DirectoryVendor.ACTIVE_DIRECTORY )
             {
-                returnRecords.addAll( checkAd( pwmDomain, config, ldapProfile ) );
+                returnRecords.addAll( checkAd( sessionLabel, pwmDomain, ldapProfile ) );
             }
 
             if ( testContextlessRoot )
@@ -654,7 +657,11 @@ public class LDAPHealthChecker implements HealthSupplier
         return returnRecords;
     }
 
-    private static List<HealthRecord> checkAd( final PwmDomain pwmDomain, final DomainConfig config, final LdapProfile ldapProfile )
+    private static List<HealthRecord> checkAd(
+            final SessionLabel sessionLabel,
+            final PwmDomain pwmDomain,
+            final LdapProfile ldapProfile
+    )
     {
         final List<HealthRecord> returnList = new ArrayList<>();
         final List<String> serverURLs = ldapProfile.readSettingAsStringArray( PwmSetting.LDAP_SERVER_URLS );
@@ -694,7 +701,7 @@ public class LDAPHealthChecker implements HealthSupplier
             }
         }
 
-        returnList.addAll( checkAdPasswordPolicyApi( pwmDomain ) );
+        returnList.addAll( checkAdPasswordPolicyApi( sessionLabel, pwmDomain ) );
 
         return returnList;
     }
@@ -711,7 +718,7 @@ public class LDAPHealthChecker implements HealthSupplier
         return false;
     }
 
-    private List<HealthRecord> checkVendorSameness( final PwmDomain pwmDomain )
+    private List<HealthRecord> checkVendorSameness( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
         final Map<HealthService.HealthMonitorFlag, Serializable> healthProperties = pwmDomain.getPwmApplication().getHealthMonitor().getHealthProperties();
         if ( healthProperties.containsKey( HealthService.HealthMonitorFlag.LdapVendorSameCheck ) )
@@ -719,7 +726,7 @@ public class LDAPHealthChecker implements HealthSupplier
             return ( List<HealthRecord> ) healthProperties.get( HealthService.HealthMonitorFlag.LdapVendorSameCheck );
         }
 
-        LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "beginning check for replica vendor sameness" );
+        LOGGER.trace( sessionLabel, () -> "beginning check for replica vendor sameness" );
         boolean errorReachingServer = false;
         final Map<String, DirectoryVendor> replicaVendorMap = new HashMap<>();
 
@@ -742,7 +749,7 @@ public class LDAPHealthChecker implements HealthSupplier
         catch ( final Exception e )
         {
             errorReachingServer = true;
-            LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL, () -> "error during replica vendor sameness check: " + e.getMessage() );
+            LOGGER.error( sessionLabel, () -> "error during replica vendor sameness check: " + e.getMessage() );
         }
 
         final ArrayList<HealthRecord> healthRecords = new ArrayList<>();
@@ -765,7 +772,7 @@ public class LDAPHealthChecker implements HealthSupplier
             // cache the error
             healthProperties.put( HealthService.HealthMonitorFlag.LdapVendorSameCheck, healthRecords );
 
-            LOGGER.warn( SessionLabel.HEALTH_SESSION_LABEL, () -> "multiple ldap vendors found: " + vendorMsg.toString() );
+            LOGGER.warn( sessionLabel, () -> "multiple ldap vendors found: " + vendorMsg.toString() );
         }
         else if ( discoveredVendors.size() == 1 )
         {
@@ -779,7 +786,7 @@ public class LDAPHealthChecker implements HealthSupplier
         return healthRecords;
     }
 
-    private static List<HealthRecord> checkAdPasswordPolicyApi( final PwmDomain pwmDomain )
+    private static List<HealthRecord> checkAdPasswordPolicyApi( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
         final boolean passwordPolicyApiEnabled = pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET );
         if ( !passwordPolicyApiEnabled )
@@ -797,7 +804,7 @@ public class LDAPHealthChecker implements HealthSupplier
             }
         }
 
-        LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "beginning check for ad api password policy (asn "
+        LOGGER.trace( sessionLabel, () -> "beginning check for ad api password policy (asn "
                 + PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN + ") support" );
         boolean errorReachingServer = false;
         final ArrayList<HealthRecord> healthRecords = new ArrayList<>();
@@ -839,7 +846,7 @@ public class LDAPHealthChecker implements HealthSupplier
         catch ( final Exception e )
         {
             errorReachingServer = true;
-            LOGGER.error( SessionLabel.HEALTH_SESSION_LABEL,
+            LOGGER.error( sessionLabel,
                     () ->  "error during ad api password policy (asn " + PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN + ") check: " + e.getMessage() );
         }
 
@@ -852,7 +859,7 @@ public class LDAPHealthChecker implements HealthSupplier
         return healthRecords;
     }
 
-    private static List<HealthRecord> checkUserPermissionValues( final PwmDomain pwmDomain )
+    private static List<HealthRecord> checkUserPermissionValues( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
         final List<HealthRecord> returnList = new ArrayList<>();
         final DomainConfig config = pwmDomain.getConfig();
@@ -867,7 +874,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     {
                         try
                         {
-                            returnList.addAll( checkUserPermission( pwmDomain, userPermission, pwmSetting ) );
+                            returnList.addAll( checkUserPermission( sessionLabel, pwmDomain, userPermission, pwmSetting ) );
                         }
                         catch ( final PwmUnrecoverableException e )
                         {
@@ -880,7 +887,9 @@ public class LDAPHealthChecker implements HealthSupplier
         return returnList;
     }
 
-    private static List<HealthRecord> checkLdapDNSyntaxValues( final PwmDomain pwmDomain )
+    private static List<HealthRecord> checkLdapDNSyntaxValues(
+            final SessionLabel sessionLabel,
+            final PwmDomain pwmDomain )
     {
         final List<HealthRecord> returnList = new ArrayList<>();
         final DomainConfig config = pwmDomain.getConfig();
@@ -901,7 +910,7 @@ public class LDAPHealthChecker implements HealthSupplier
                             final String value = config.getLdapProfiles().get( profile ).readSettingAsString( pwmSetting );
                             if ( value != null && !value.isEmpty() )
                             {
-                                final Optional<String> errorMsg = validateDN( pwmDomain, value, profile );
+                                final Optional<String> errorMsg = validateDN( sessionLabel, pwmDomain, value, profile );
                                 errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
                                         pwmDomain.getDomainID(),
                                         HealthMessage.Config_DNValueValidity,
@@ -916,7 +925,7 @@ public class LDAPHealthChecker implements HealthSupplier
                             {
                                 for ( final String value : values )
                                 {
-                                    final Optional<String> errorMsg = validateDN( pwmDomain, value, profile );
+                                    final Optional<String> errorMsg = validateDN( sessionLabel, pwmDomain, value, profile );
                                     errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
                                             pwmDomain.getDomainID(),
                                             HealthMessage.Config_DNValueValidity,
@@ -938,6 +947,7 @@ public class LDAPHealthChecker implements HealthSupplier
     }
 
     private static List<HealthRecord> checkNewUserPasswordTemplateSetting(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final DomainConfig domainConfig
     )
@@ -969,7 +979,7 @@ public class LDAPHealthChecker implements HealthSupplier
                 final LdapProfile ldapProfile = newUserProfile.getLdapProfile( pwmDomain.getConfig() );
                 if ( NewUserProfile.TEST_USER_CONFIG_VALUE.equals( policyUserStr ) )
                 {
-                    final UserIdentity testUser = ldapProfile.getTestUser( pwmDomain );
+                    final UserIdentity testUser = ldapProfile.getTestUser( sessionLabel, pwmDomain );
                     if ( testUser != null )
                     {
                         return Collections.emptyList();
@@ -978,7 +988,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
                 final UserIdentity newUserTemplateIdentity = UserIdentity.create( policyUserStr, ldapProfile.getIdentifier(), pwmDomain.getDomainID() );
 
-                final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( newUserTemplateIdentity );
+                final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( sessionLabel, newUserTemplateIdentity );
 
                 try
                 {
@@ -1009,6 +1019,7 @@ public class LDAPHealthChecker implements HealthSupplier
     }
 
     private static List<HealthRecord> checkUserSearching(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain
     )
     {
@@ -1029,7 +1040,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     .username( healthUsername )
                     .build();
 
-            pwmDomain.getUserSearchEngine().performMultiUserSearch( searchConfiguration, 1, Collections.singletonList( "cn" ), SessionLabel.HEALTH_SESSION_LABEL );
+            pwmDomain.getUserSearchEngine().performMultiUserSearch( searchConfiguration, 1, Collections.singletonList( "cn" ), sessionLabel );
         }
         catch ( final Exception e )
         {
@@ -1057,6 +1068,7 @@ public class LDAPHealthChecker implements HealthSupplier
     }
 
     private static List<HealthRecord> checkUserPermission(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final UserPermission userPermission,
             final PwmSetting pwmSetting
@@ -1104,7 +1116,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     final String userDN = userPermission.getLdapBase();
                     if ( userDN != null && !isExampleDN( userDN ) )
                     {
-                        final Optional<String> errorMsg = validateDN( pwmDomain, userDN, ldapProfileID );
+                        final Optional<String> errorMsg = validateDN( sessionLabel, pwmDomain, userDN, ldapProfileID );
                         errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.Config_UserPermissionValidity,
@@ -1118,7 +1130,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     final String groupDN = userPermission.getLdapBase();
                     if ( groupDN != null && !isExampleDN( groupDN ) )
                     {
-                        final Optional<String> errorMsg = validateDN( pwmDomain, groupDN, ldapProfileID );
+                        final Optional<String> errorMsg = validateDN( sessionLabel, pwmDomain, groupDN, ldapProfileID );
                         errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.Config_UserPermissionValidity,
@@ -1133,7 +1145,7 @@ public class LDAPHealthChecker implements HealthSupplier
                     final String baseDN = userPermission.getLdapBase();
                     if ( baseDN != null && !isExampleDN( baseDN ) )
                     {
-                        final Optional<String> errorMsg = validateDN( pwmDomain, baseDN, ldapProfileID );
+                        final Optional<String> errorMsg = validateDN( sessionLabel, pwmDomain, baseDN, ldapProfileID );
                         errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
                                 pwmDomain.getDomainID(),
                                 HealthMessage.Config_UserPermissionValidity,
@@ -1151,6 +1163,7 @@ public class LDAPHealthChecker implements HealthSupplier
     }
 
     private static Optional<String> validateDN(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final String dnValue,
             final String ldapProfileID
@@ -1162,7 +1175,7 @@ public class LDAPHealthChecker implements HealthSupplier
             return Optional.empty();
         }
 
-        final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( ldapProfileID );
+        final ChaiProvider chaiProvider = pwmDomain.getProxyChaiProvider( sessionLabel, ldapProfileID );
         try
         {
             if ( !isExampleDN( dnValue ) )
@@ -1216,7 +1229,8 @@ public class LDAPHealthChecker implements HealthSupplier
         return false;
     }
 
-    public static HealthData healthForNewConfiguration(
+    public static PublicHealthData healthForNewConfiguration(
+            final SessionLabel sessionLabel,
             final PwmDomain pwmDomain,
             final DomainConfig config,
             final Locale locale,
@@ -1235,11 +1249,11 @@ public class LDAPHealthChecker implements HealthSupplier
 
         final LdapProfile ldapProfile = config.getLdapProfiles().get( profileID );
         final List<HealthRecord> profileRecords = new ArrayList<>(
-                ldapHealthChecker.checkBasicLdapConnectivity( tempDomain, config, ldapProfile, testContextless ) );
+                ldapHealthChecker.checkBasicLdapConnectivity( sessionLabel, tempDomain, config, ldapProfile, testContextless ) );
 
         if ( fullTest )
         {
-            profileRecords.addAll( ldapHealthChecker.checkLdapServerUrls( tempDomain, config, ldapProfile ) );
+            profileRecords.addAll( ldapHealthChecker.checkLdapServerUrls( sessionLabel, tempDomain, config, ldapProfile ) );
         }
 
         if ( profileRecords.isEmpty() )
@@ -1249,7 +1263,7 @@ public class LDAPHealthChecker implements HealthSupplier
 
         if ( fullTest )
         {
-            profileRecords.addAll( ldapHealthChecker.doLdapTestUserCheck( config, ldapProfile, tempDomain ) );
+            profileRecords.addAll( ldapHealthChecker.doLdapTestUserCheck( sessionLabel, config, ldapProfile, tempDomain ) );
         }
 
         return HealthRecord.asHealthDataBean( config, locale, profileRecords );

+ 2 - 2
server/src/main/java/password/pwm/health/LocalDBHealthChecker.java

@@ -35,10 +35,10 @@ import java.util.function.Supplier;
 
 public class LocalDBHealthChecker implements HealthSupplier
 {
-
     @Override
-    public List<Supplier<List<HealthRecord>>> jobs( final PwmApplication pwmApplication )
+    public List<Supplier<List<HealthRecord>>> jobs( final HealthSupplierRequest request )
     {
+        final PwmApplication pwmApplication = request.getPwmApplication();
         final Supplier<List<HealthRecord>> supplier = () -> doHealthCheck( pwmApplication );
         return Collections.singletonList( supplier );
     }

+ 3 - 35
server/src/main/java/password/pwm/http/HttpEventManager.java

@@ -27,6 +27,7 @@ import password.pwm.bean.DomainID;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.stats.EpsStatistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -68,10 +69,7 @@ public class HttpEventManager implements
             final PwmApplication pwmApplication = contextManager.getPwmApplication();
             httpSession.setAttribute( PwmConstants.SESSION_ATTR_PWM_APP_NONCE, pwmApplication.getRuntimeNonce() );
 
-            if ( pwmApplication.getStatisticsManager() != null )
-            {
-                pwmApplication.getStatisticsManager().updateEps( EpsStatistic.SESSIONS, 1 );
-            }
+            StatisticsClient.updateEps( pwmApplication, EpsStatistic.SESSIONS );
 
             LOGGER.trace( () -> "new http session created" );
 
@@ -164,41 +162,11 @@ public class HttpEventManager implements
     @Override
     public void sessionWillPassivate( final HttpSessionEvent event )
     {
-        /*
-        try
-        {
-            final PwmSession pwmSession = PwmSessionFactory.readPwmSession( event.getSession() );
-            LOGGER.trace( pwmSession.getLabel(), () -> "passivating session" );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.error( () -> "unable to passivate session: " + e.getMessage() );
-        }
-
-         */
     }
 
     @Override
     public void sessionDidActivate( final HttpSessionEvent event )
     {
-        /*
-        try
-        {
-            final HttpSession httpSession = event.getSession();
-            final PwmSession pwmSession = PwmSessionFactory.readPwmSession( httpSession );
-            LOGGER.trace( pwmSession.getLabel(), () -> "activating (de-passivating) session" );
-            final PwmApplication pwmApplication = ContextManager.getPwmApplication( httpSession.getServletContext() );
-            if ( pwmApplication != null )
-            {
-                pwmApplication.getSessionTrackService().addSessionData( pwmSession );
-            }
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.error( () -> "unable to activate (de-passivate) session: " + e.getMessage() );
-        }
-
-         */
     }
 
     private static String makeSessionDestroyedDebugMsg( final PwmSession pwmSession )
@@ -210,7 +178,7 @@ public class HttpEventManager implements
         final Instant lastAccessedTime = sessionStateBean.getSessionLastAccessedTime();
         final TimeDuration timeDuration = TimeDuration.between( startTime, lastAccessedTime );
         debugItems.put( "firstToLastRequestInterval", timeDuration.asCompactString() );
-        final TimeDuration avgReqDuration = TimeDuration.of( sessionStateBean.getAvgRequestDuration().getLastMillis(), TimeDuration.Unit.MILLISECONDS );
+        final TimeDuration avgReqDuration =  sessionStateBean.getAvgRequestDuration().getAverageAsDuration();
         debugItems.put( "avgRequestDuration", avgReqDuration.asCompactString() );
         return StringHelper.stringMapToString( debugItems, "," );
     }

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

@@ -57,6 +57,7 @@ public enum HttpHeader
 
 
     XAmb( "X-" + PwmConstants.PWM_APP_NAME + "-Amb" ),
+    XDomain( "X-" + PwmConstants.PWM_APP_NAME + "-Domain" ),
     XVersion( "X-" + PwmConstants.PWM_APP_NAME + "-Version" ),
     XInstance( "X-" + PwmConstants.PWM_APP_NAME + "-Instance" ),
     XSessionID( "X-" + PwmConstants.PWM_APP_NAME + "-SessionID" ),

+ 3 - 7
server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java

@@ -20,14 +20,12 @@
 
 package password.pwm.http;
 
-import lombok.AllArgsConstructor;
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
+import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
@@ -157,9 +155,7 @@ public class IdleTimeoutCalculator
         return Collections.unmodifiableSet( results );
     }
 
-    @Getter
-    @AllArgsConstructor
-    @EqualsAndHashCode
+    @Value
     static class MaxIdleTimeoutResult implements Comparable<MaxIdleTimeoutResult>
     {
         private final String reason;

+ 14 - 22
server/src/main/java/password/pwm/http/PwmCookiePath.java

@@ -23,33 +23,25 @@ package password.pwm.http;
 import password.pwm.PwmConstants;
 import password.pwm.error.PwmUnrecoverableException;
 
+import java.util.function.Function;
+
 public enum PwmCookiePath
 {
-    Domain,
-    Private,
-    CurrentURL,
-    PwmServlet,;
+    Domain( ( pwmRequest ) -> "/" ),
+    Private( ( pwmRequest ) -> PwmConstants.URL_PREFIX_PRIVATE ),
+    CurrentURL( ( pwmRequest ) -> pwmRequest.getURL().toString() ),
+    PwmServlet( ( pwmRequest ) -> pwmRequest.getURL().determinePwmServletPath() ),;
+
+    private final transient Function<PwmRequest, String> suffixFunction;
+
+    PwmCookiePath( final Function<PwmRequest, String> suffixFunction )
+    {
+        this.suffixFunction = suffixFunction;
+    }
 
     String toStringPath( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        switch ( this )
-        {
-            case Domain:
-                return pwmRequest.getBasePath() + "/";
-
-            case Private:
-                return pwmRequest.getBasePath() + PwmConstants.URL_PREFIX_PRIVATE;
-
-            case CurrentURL:
-                return pwmRequest.getURL().toString();
-
-            case PwmServlet:
-                return pwmRequest.getURL().determinePwmServletPath();
-
-            default:
-                throw new IllegalStateException( "undefined CookiePath type: " + this );
-        }
-
+        return pwmRequest.getBasePath() + suffixFunction.apply( pwmRequest );
     }
 }

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

@@ -187,7 +187,7 @@ public class PwmHttpRequestWrapper
         return Collections.unmodifiableMap( outputMap );
     }
 
-    public PasswordData readParameterAsPassword( final String name )
+    public Optional<PasswordData> readParameterAsPassword( final String name )
             throws PwmUnrecoverableException
     {
         final int maxLength = Integer.parseInt( appConfig.readAppProperty( AppProperty.HTTP_PARAM_MAX_READ_LENGTH ) );
@@ -201,10 +201,10 @@ public class PwmHttpRequestWrapper
             if ( sanitizedValue != null )
             {
                 final String trimmedVale = trim ? sanitizedValue.trim() : sanitizedValue;
-                return new PasswordData( trimmedVale );
+                return Optional.of( new PasswordData( trimmedVale ) );
             }
         }
-        return null;
+        return Optional.empty();
     }
 
     public String readParameterAsString( final String name, final int maxLength, final Flag... flags )

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

@@ -35,6 +35,7 @@ import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.AppConfig;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.AccountInformationProfile;
@@ -149,12 +150,7 @@ public class PwmRequest extends PwmHttpRequestWrapper
 
     public PwmSession getPwmSession( )
     {
-        return getPwmSession( this.getPwmDomain() );
-    }
-
-    public PwmSession getPwmSession( final PwmDomain pwmDomain )
-    {
-        return PwmSessionFactory.readPwmSession( this.getHttpServletRequest().getSession(), pwmDomain );
+        return PwmSessionFactory.readPwmSession( this.getHttpServletRequest().getSession(), getPwmDomain() );
     }
 
     public SessionLabel getLabel( )
@@ -184,7 +180,9 @@ public class PwmRequest extends PwmHttpRequestWrapper
         {
             return PwmConstants.DEFAULT_LOCALE;
         }
-        return getPwmSession().getSessionStateBean().getLocale();
+
+        final Locale userLocale = getPwmSession().getSessionStateBean().getLocale();
+        return userLocale != null ? userLocale : PwmConstants.DEFAULT_LOCALE;
     }
 
     public void forwardToJsp( final JspUrl jspURL )
@@ -612,9 +610,10 @@ public class PwmRequest extends PwmHttpRequestWrapper
     {
         final String rawContextPath = this.getHttpServletRequest().getContextPath();
 
-        if ( getAppConfig().isMultiDomain() )
+        final AppConfig appConfig = getAppConfig();
+        if ( appConfig.isMultiDomain() && appConfig.readSettingAsBoolean( PwmSetting.DOMAIN_DOMAIN_PATHS ) )
         {
-            return rawContextPath + "/" + this.getDomainID().stringValue();
+            return rawContextPath + "/" + StringUtil.urlPathEncode( this.getDomainID().stringValue() );
         }
 
         return rawContextPath;

+ 4 - 8
server/src/main/java/password/pwm/http/PwmSession.java

@@ -39,6 +39,7 @@ import password.pwm.ldap.UserInfoBean;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
@@ -105,10 +106,7 @@ public class PwmSession implements Serializable
 
         this.sessionStateBean.setSessionLastAccessedTime( Instant.now() );
 
-        if ( pwmDomain.getStatisticsManager() != null )
-        {
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.HTTP_SESSIONS );
-        }
+        StatisticsClient.incrementStat( pwmDomain.getPwmApplication(), Statistic.HTTP_SESSIONS );
 
         pwmDomain.getSessionTrackService().addSessionData( this );
         this.sessionManager = new SessionManager( pwmDomain, this );
@@ -160,7 +158,7 @@ public class PwmSession implements Serializable
                     pwmRequest.getLabel(),
                     getSessionStateBean().getLocale(),
                     oldUserInfoBean.getUserIdentity(),
-                    pwmDomain.getProxyChaiProvider( oldUserInfoBean.getUserIdentity().getLdapProfileID() )
+                    pwmDomain.getProxyChaiProvider( pwmRequest.getLabel(), oldUserInfoBean.getUserIdentity().getLdapProfileID() )
             );
         }
         else
@@ -207,7 +205,6 @@ public class PwmSession implements Serializable
 
         UserIdentity userIdentity = null;
         String userID = null;
-        String domain = null;
         String profile = null;
 
         if ( isAuthenticated() )
@@ -218,7 +215,6 @@ public class PwmSession implements Serializable
                 userIdentity = userInfo.getUserIdentity();
                 userID = userInfo.getUsername();
                 profile = userIdentity.getLdapProfileID();
-                domain = userIdentity.getDomainID().toString();
             }
             catch ( final PwmUnrecoverableException e )
             {
@@ -230,7 +226,7 @@ public class PwmSession implements Serializable
                 .sessionID( ssBean.getSessionID() )
                 .userID( userIdentity == null ? null : userIdentity.toDelimitedKey() )
                 .username( userID )
-                .domain( domain )
+                .domain( domainID.stringValue() )
                 .profile( profile )
                 .sourceAddress( ssBean.getSrcAddress() )
                 .sourceHostname( ssBean.getSrcHostname() )

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

@@ -32,7 +32,7 @@ import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.logging.PwmLogger;
 
@@ -84,7 +84,7 @@ public class BasicFilterAuthenticationProvider implements PwmHttpFilterAuthentic
         {
             if ( e.getError() == PwmError.ERROR_DIRECTORY_UNAVAILABLE )
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.LDAP_UNAVAILABLE_COUNT );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.LDAP_UNAVAILABLE_COUNT );
             }
             throw new PwmUnrecoverableException( e.getError() );
         }

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

@@ -20,12 +20,14 @@
 
 package password.pwm.http.bean;
 
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 
 import java.io.Serializable;
 import java.util.List;
 
 @Getter
+@EqualsAndHashCode
 public class DisplayElement implements Serializable
 {
     private String key;

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

@@ -22,7 +22,7 @@ package password.pwm.http.bean;
 
 import password.pwm.config.option.SessionBeanMode;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.operations.otp.OTPUserRecord;
+import password.pwm.svc.otp.OTPUserRecord;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;

+ 213 - 0
server/src/main/java/password/pwm/http/filter/DomainInitFilter.java

@@ -0,0 +1,213 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 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.filter;
+
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
+import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmURL;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class DomainInitFilter implements Filter
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( DomainInitFilter.class );
+
+    @Override
+    public void init( final FilterConfig filterConfig )
+            throws ServletException
+    {
+    }
+
+    @Override
+    public void destroy()
+    {
+    }
+
+    @Override
+    public void doFilter(
+            final ServletRequest servletRequest,
+            final ServletResponse servletResponse,
+            final FilterChain filterChain
+    )
+            throws IOException, ServletException
+    {
+        final HttpServletRequest req = ( HttpServletRequest ) servletRequest;
+        final HttpServletResponse resp = ( HttpServletResponse ) servletResponse;
+
+        final PwmApplication localPwmApplication;
+        try
+        {
+            localPwmApplication = ContextManager.getPwmApplication( req );
+        }
+        catch ( final PwmException e )
+        {
+            LOGGER.error( () -> "unable to load pwmApplication: " + e.getMessage() );
+            throw new ServletException( e.getMessage() );
+        }
+
+        if ( initializeDomainIdInRequest( localPwmApplication, req, resp ) == ProcessStatus.Halt )
+        {
+            return;
+        }
+
+        filterChain.doFilter( req, resp );
+    }
+
+    ProcessStatus initializeDomainIdInRequest(
+            final PwmApplication pwmApplication,
+            final HttpServletRequest req,
+            final HttpServletResponse resp
+    )
+            throws IOException
+    {
+        if ( pwmApplication.isMultiDomain() )
+        {
+            final Optional<DomainID> requestDomainID = readDomainFromRequest( pwmApplication, req );
+            if ( requestDomainID.isPresent() )
+            {
+                req.setAttribute( PwmConstants.REQUEST_ATTR_DOMAIN, requestDomainID.get().stringValue() );
+            }
+            else
+            {
+                try
+                {
+                    final boolean pathMode = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.DOMAIN_DOMAIN_PATHS );
+                    if ( pathMode )
+                    {
+                        final DomainID redirectDomain = pwmApplication.getAdminDomain().getDomainID();
+                        final String redirectUrl = req.getContextPath() + "/" + redirectDomain.stringValue() + "/";
+                        resp.sendRedirect( redirectUrl );
+                        LOGGER.debug( () -> "request does not indicate domain, redirecting to admin domain url: " + redirectUrl );
+                        return ProcessStatus.Halt;
+                    }
+                    else
+                    {
+                        throw new IllegalStateException( "domain not specified in request and admin domain is not configured." );
+                    }
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    final String msg = "error redirecting non-domain request to admin domain: " + e.getMessage();
+                    resp.sendError( 500, msg );
+                    LOGGER.error( () -> msg );
+                    return ProcessStatus.Halt;
+                }
+            }
+        }
+        else
+        {
+            final String domainStr = pwmApplication.getConfig().getDomainIDs().iterator().next();
+            req.setAttribute( PwmConstants.REQUEST_ATTR_DOMAIN, domainStr );
+        }
+
+        return ProcessStatus.Continue;
+    }
+
+    private static Optional<DomainID> readDomainFromRequest( final PwmApplication pwmApplication, final HttpServletRequest req )
+    {
+        final boolean pathMode = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.DOMAIN_DOMAIN_PATHS );
+        if ( pathMode )
+        {
+            final Optional<DomainID> readDomainID = readDomainFromPathRequest( pwmApplication, req );
+            if ( readDomainID.isPresent() )
+            {
+                return readDomainID;
+            }
+        }
+
+        final Optional<DomainID> readDomainID = readDomainFromDomainRequest( pwmApplication, req );
+        if ( readDomainID.isPresent() )
+        {
+            return readDomainID;
+        }
+
+        if ( !pathMode )
+        {
+            try
+            {
+                return Optional.of( pwmApplication.getAdminDomain().getDomainID() );
+            }
+            catch ( final PwmUnrecoverableException e )
+            {
+                throw new IllegalStateException( "domain not specified in request and admin domain is not configured." );
+            }
+        }
+
+        return Optional.empty();
+    }
+
+    private static Optional<DomainID> readDomainFromDomainRequest( final PwmApplication pwmApplication, final HttpServletRequest req )
+    {
+        final URI uri = URI.create( req.getRequestURL().toString() );
+        final String host = uri.getHost();
+
+        for ( final PwmDomain pwmDomain : pwmApplication.domains().values() )
+        {
+            final List<String> hostMatches = pwmDomain.getConfig().readSettingAsStringArray( PwmSetting.DOMAIN_HOSTS );
+            if ( hostMatches.contains( host ) )
+            {
+                return Optional.of( pwmDomain.getDomainID() );
+            }
+        }
+
+        return Optional.empty();
+    }
+
+    private static Optional<DomainID> readDomainFromPathRequest( final PwmApplication pwmApplication, final HttpServletRequest req )
+    {
+        final PwmURL pwmURL = PwmURL.create( req, pwmApplication.getConfig() );
+        final List<String> urlPaths = pwmURL.splitPaths();
+        if ( urlPaths.size() <= 1 )
+        {
+            return Optional.empty();
+        }
+
+        final String domainPath = urlPaths.get( 1 );
+
+        final Set<String> domains = pwmApplication.getConfig().getDomainIDs();
+
+        if ( domains.contains( domainPath ) )
+        {
+            return Optional.of( DomainID.create( domainPath ) );
+        }
+        return Optional.empty();
+    }
+}

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

@@ -30,7 +30,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
@@ -103,7 +103,7 @@ public class ObsoleteUrlFilter extends AbstractPwmFilter
                             + requestServletUrl
                             + "' detected, redirecting to canonical URL of '"
                             + pwmServletDefinition.servletUrl() + "'" );
-                    StatisticsManager.incrementStat( pwmRequest, Statistic.OBSOLETE_URL_REQUESTS );
+                    StatisticsClient.incrementStat( pwmRequest, Statistic.OBSOLETE_URL_REQUESTS );
                     pwmRequest.getPwmResponse().sendRedirect( pwmServletDefinition );
                     return ProcessStatus.Halt;
                 }

+ 24 - 82
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -25,7 +25,6 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
-import password.pwm.bean.DomainID;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -37,7 +36,7 @@ import password.pwm.http.ContextManager;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.IdleTimeoutCalculator;
 import password.pwm.http.JspUrl;
-import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmResponse;
@@ -46,7 +45,7 @@ import password.pwm.http.PwmSessionFactory;
 import password.pwm.http.PwmURL;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsService;
 import password.pwm.util.IPMatcher;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
@@ -76,11 +75,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 
 public class RequestInitializationFilter implements Filter
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( RequestInitializationFilter.class );
 
     @Override
@@ -98,7 +95,9 @@ public class RequestInitializationFilter implements Filter
     public void doFilter(
             final ServletRequest servletRequest,
             final ServletResponse servletResponse,
-            final FilterChain filterChain ) throws IOException, ServletException
+            final FilterChain filterChain
+    )
+            throws IOException, ServletException
     {
 
         final HttpServletRequest req = ( HttpServletRequest ) servletRequest;
@@ -116,11 +115,6 @@ public class RequestInitializationFilter implements Filter
             throw new ServletException( e.getMessage() );
         }
 
-        if ( initializeDomainIdInRequest( localPwmApplication, req, resp ) == ProcessStatus.Halt )
-        {
-            return;
-        }
-
         final PwmURL pwmURL = PwmURL.create( req, localPwmApplication.getConfig() );
 
         if ( pwmURL.isResourceURL() )
@@ -164,8 +158,6 @@ public class RequestInitializationFilter implements Filter
             return;
         }
 
-
-
         try
         {
             localPwmApplication.getActiveServletRequests().incrementAndGet();
@@ -359,8 +351,7 @@ public class RequestInitializationFilter implements Filter
             resp.setHeader( HttpHeader.ContentLanguage, pwmRequest.getLocale().toLanguageTag() );
         }
 
-        addStaticResponseHeaders( pwmApplication, resp.getHttpServletResponse() );
-
+        addStaticResponseHeaders( pwmApplication, pwmRequest.getHttpServletRequest(), resp.getHttpServletResponse() );
 
         if ( pwmSession != null )
         {
@@ -386,6 +377,7 @@ public class RequestInitializationFilter implements Filter
 
     public static void addStaticResponseHeaders(
             final PwmApplication pwmApplication,
+            final HttpServletRequest req,
             final HttpServletResponse resp
     )
             throws PwmUnrecoverableException
@@ -399,6 +391,7 @@ public class RequestInitializationFilter implements Filter
         final boolean includeXXSSProtection = Boolean.parseBoolean( config.readAppProperty( AppProperty.HTTP_HEADER_SEND_XXSSPROTECTION ) );
         final boolean includeXFrameDeny = config.readSettingAsBoolean( PwmSetting.SECURITY_PREVENT_FRAMING );
         final boolean includeXAmb = Boolean.parseBoolean( config.readAppProperty( AppProperty.HTTP_HEADER_SEND_XAMB ) );
+        final boolean includeDomain = Boolean.parseBoolean( config.readAppProperty( AppProperty.HTTP_HEADER_SEND_XDOMAIN ) );
 
         makeNoiseHeader( pwmApplication, config ).ifPresent( noiseHeader -> resp.setHeader( HttpHeader.XNoise.getHttpName(), noiseHeader ) );
 
@@ -440,7 +433,18 @@ public class RequestInitializationFilter implements Filter
             ) );
         }
 
-        resp.setHeader( HttpHeader.CacheControl.getHttpName(), "no-cache, no-store, must-revalidate, proxy-revalidate" );
+        if ( includeDomain && pwmApplication.isMultiDomain() )
+        {
+            resp.setHeader( HttpHeader.XDomain.getHttpName(), PwmHttpRequestWrapper.readDomainIdFromRequest( req ).stringValue() );
+        }
+
+        {
+            final String cacheControl = pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_HEADER_CACHE_CONTROL );
+            if ( StringUtil.notEmpty( cacheControl ) )
+            {
+                resp.setHeader( HttpHeader.CacheControl.getHttpName(), cacheControl );
+            }
+        }
     }
 
 
@@ -617,7 +621,7 @@ public class RequestInitializationFilter implements Filter
         checkTrial( pwmRequest );
 
         // check intruder
-        pwmRequest.getPwmDomain().getIntruderManager().convenience().checkAddressAndSession( pwmRequest.getPwmSession() );
+        pwmRequest.getPwmDomain().getIntruderService().client().checkAddressAndSession( pwmRequest.getPwmSession() );
     }
 
     private static void checkIfSourceAddressChanged( final PwmRequest pwmRequest )
@@ -813,14 +817,14 @@ public class RequestInitializationFilter implements Filter
     {
         if ( PwmConstants.TRIAL_MODE )
         {
-            final StatisticsManager statisticsManager = pwmRequest.getPwmDomain().getStatisticsManager();
-            final String currentAuthString = statisticsManager.getStatBundleForKey( StatisticsManager.KEY_CURRENT ).getStatistic( Statistic.AUTHENTICATIONS );
+            final StatisticsService statisticsManager = pwmRequest.getPwmDomain().getStatisticsManager();
+            final String currentAuthString = statisticsManager.getStatBundleForKey( StatisticsService.KEY_CURRENT ).getStatistic( Statistic.AUTHENTICATIONS );
             if ( new BigInteger( currentAuthString ).compareTo( BigInteger.valueOf( PwmConstants.TRIAL_MAX_AUTHENTICATIONS ) ) > 0 )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TRIAL_VIOLATION, "maximum usage per server startup exceeded" ) );
             }
 
-            final String totalAuthString = statisticsManager.getStatBundleForKey( StatisticsManager.KEY_CUMULATIVE ).getStatistic( Statistic.AUTHENTICATIONS );
+            final String totalAuthString = statisticsManager.getStatBundleForKey( StatisticsService.KEY_CUMULATIVE ).getStatistic( Statistic.AUTHENTICATIONS );
             if ( new BigInteger( totalAuthString ).compareTo( BigInteger.valueOf( PwmConstants.TRIAL_MAX_TOTAL_AUTH ) ) > 0 )
             {
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TRIAL_VIOLATION, "maximum usage for this server has been exceeded" ) );
@@ -870,66 +874,4 @@ public class RequestInitializationFilter implements Filter
 
         return Optional.empty();
     }
-
-    ProcessStatus initializeDomainIdInRequest(
-            final PwmApplication pwmApplication,
-            final HttpServletRequest req,
-            final HttpServletResponse resp
-    )
-            throws IOException
-    {
-        if ( pwmApplication.isMultiDomain() )
-        {
-            final Optional<DomainID> requestDomainID = readDomainFromRequest( pwmApplication, req );
-            if ( requestDomainID.isPresent() )
-            {
-                req.setAttribute( PwmConstants.REQUEST_ATTR_DOMAIN, requestDomainID.get().stringValue() );
-            }
-            else
-            {
-                try
-                {
-                    final DomainID redirectDomain = pwmApplication.getAdminDomain().getDomainID();
-                    final String redirectUrl = req.getContextPath() + "/" + redirectDomain.stringValue() + "/";
-                    resp.sendRedirect( redirectUrl );
-                    LOGGER.debug( () -> "request does not indicate domain, redirecting to admin domain url: " + redirectUrl );
-                    return ProcessStatus.Halt;
-                }
-                catch ( final PwmUnrecoverableException e )
-                {
-                    final String msg = "error redirecting non-domain request to admin domain: " + e.getMessage();
-                    resp.sendError( 500, msg );
-                    LOGGER.error( () -> msg );
-                    return ProcessStatus.Halt;
-                }
-            }
-        }
-        else
-        {
-            final String domainStr = pwmApplication.getConfig().getDomainIDs().iterator().next();
-            req.setAttribute( PwmConstants.REQUEST_ATTR_DOMAIN, domainStr );
-        }
-
-        return ProcessStatus.Continue;
-    }
-
-    public static Optional<DomainID> readDomainFromRequest( final PwmApplication pwmApplication, final HttpServletRequest req )
-    {
-        final PwmURL pwmURL = PwmURL.create( req, pwmApplication.getConfig() );
-        final List<String> urlPaths = pwmURL.splitPaths();
-        if ( urlPaths.size() <= 1 )
-        {
-            return Optional.empty();
-        }
-
-        final String domainPath = urlPaths.get( 1 );
-
-        final Set<String> domains = pwmApplication.getConfig().getDomainIDs();
-
-        if ( domains.contains( domainPath ) )
-        {
-            return Optional.of( DomainID.create( domainPath ) );
-        }
-        return Optional.empty();
-    }
 }

+ 3 - 5
server/src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -45,6 +45,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.svc.stats.AvgStatistic;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -137,7 +138,7 @@ public class SessionFilter extends AbstractPwmFilter
         pwmRequest.debugHttpRequestToLog( "completed", () -> requestExecuteTime );
         pwmRequest.getPwmDomain().getStatisticsManager().updateAverageValue( AvgStatistic.AVG_REQUEST_PROCESS_TIME, requestExecuteTime.asMillis() );
         pwmRequest.getPwmSession().getSessionStateBean().getRequestCount().incrementAndGet();
-        pwmRequest.getPwmSession().getSessionStateBean().getAvgRequestDuration().update( requestExecuteTime.asMillis() );
+        pwmRequest.getPwmSession().getSessionStateBean().getAvgRequestDuration().update( requestExecuteTime );
     }
 
     private ProcessStatus handleStandardRequestOperations(
@@ -186,10 +187,7 @@ public class SessionFilter extends AbstractPwmFilter
         // update last request time.
         ssBean.setSessionLastAccessedTime( Instant.now() );
 
-        if ( pwmDomain.getStatisticsManager() != null )
-        {
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.HTTP_REQUESTS );
-        }
+        StatisticsClient.incrementStat( pwmDomain.getPwmApplication(), Statistic.HTTP_REQUESTS );
 
         return ProcessStatus.Continue;
     }

+ 9 - 24
server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java

@@ -32,8 +32,10 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.Validator;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.SecureEngine;
@@ -47,10 +49,12 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Collection;
+import java.util.function.Supplier;
 
 public abstract class AbstractPwmServlet extends HttpServlet implements PwmServlet
 {
 
+
     private static final PwmLogger LOGGER = PwmLogger.forClass( AbstractPwmServlet.class );
 
     @Override
@@ -228,17 +232,9 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
         {
             case ERROR_DIRECTORY_UNAVAILABLE:
                 LOGGER.fatal( pwmRequest.getLabel(), () -> e.getErrorInformation().toDebugStr() );
-                try
-                {
-                    pwmDomain.getStatisticsManager().incrementValue( Statistic.LDAP_UNAVAILABLE_COUNT );
-                }
-                catch ( final Throwable e1 )
-                {
-                    //noop
-                }
+                StatisticsClient.incrementStat( pwmRequest, Statistic.LDAP_UNAVAILABLE_COUNT );
                 break;
 
-
             case ERROR_PASSWORD_REQUIRED:
                 LOGGER.warn(
                         () -> "attempt to access functionality requiring password authentication, but password not yet supplied by actor, forwarding to password Login page" );
@@ -255,23 +251,12 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
                 }
                 break;
 
-
             case ERROR_INTERNAL:
             default:
-                LOGGER.fatal( pwmRequest.getLabel(), () -> "unexpected error: " + e.getErrorInformation().toDebugStr() );
-                try
-                {
-                    // try to update stats
-                    if ( pwmDomain != null )
-                    {
-                        pwmDomain.getStatisticsManager().incrementValue( Statistic.PWM_UNKNOWN_ERRORS );
-                    }
-                }
-                catch ( final Throwable e1 )
-                {
-                    //noop
-                }
-                break;
+                final Supplier<CharSequence> msg = () -> "unexpected error: " + e.getErrorInformation().toDebugStr();
+                final PwmLogLevel level = e.getError().isTrivial() ? PwmLogLevel.TRACE : PwmLogLevel.ERROR;
+                LOGGER.log( level, pwmRequest.getLabel(), msg );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PWM_UNKNOWN_ERRORS );
         }
         return false;
     }

+ 5 - 5
server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java

@@ -47,7 +47,7 @@ import password.pwm.i18n.Display;
 import password.pwm.svc.sessiontrack.UserAgentUtils;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsService;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
@@ -58,7 +58,7 @@ import password.pwm.util.secure.SecureEngine;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestHealthServer;
 import password.pwm.ws.server.rest.RestStatisticsServer;
-import password.pwm.ws.server.rest.bean.HealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthData;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -223,7 +223,7 @@ public class ClientApiServlet extends ControlledPwmServlet
 
         try
         {
-            final HealthData jsonOutput = RestHealthServer.processGetHealthCheckData(
+            final PublicHealthData jsonOutput = RestHealthServer.processGetHealthCheckData(
                     pwmRequest.getPwmDomain(),
                     pwmRequest.getLocale() );
             final RestResultBean restResultBean = RestResultBean.withData( jsonOutput );
@@ -427,7 +427,7 @@ public class ClientApiServlet extends ControlledPwmServlet
             final Map<String, Map<String, String>> ldapProfiles = new LinkedHashMap<>();
             for ( final String ldapProfile : pwmDomain.getConfig().getLdapProfiles().keySet() )
             {
-                final Map<String, String> contexts = pwmDomain.getConfig().getLdapProfiles().get( ldapProfile ).getSelectableContexts( pwmDomain );
+                final Map<String, String> contexts = pwmDomain.getConfig().getLdapProfiles().get( ldapProfile ).getSelectableContexts( pwmRequest.getLabel(), pwmDomain );
                 ldapProfiles.put( ldapProfile, contexts );
             }
             settingMap.put( "ldapProfiles", ldapProfiles );
@@ -477,7 +477,7 @@ public class ClientApiServlet extends ControlledPwmServlet
         final String statName = pwmRequest.readParameterAsString( "statName" );
         final String days = pwmRequest.readParameterAsString( "days" );
 
-        final StatisticsManager statisticsManager = pwmRequest.getPwmDomain().getStatisticsManager();
+        final StatisticsService statisticsManager = pwmRequest.getPwmDomain().getStatisticsManager();
         final RestStatisticsServer.OutputVersion1.JsonOutput jsonOutput = new RestStatisticsServer.OutputVersion1.JsonOutput();
         jsonOutput.EPS = RestStatisticsServer.OutputVersion1.addEpsStats( statisticsManager );
 

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

@@ -213,7 +213,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
     public @interface ActionHandler
     {
         String action( );
-    }
+            }
 
     private Map<String, Method> createMethodCache()
     {

+ 6 - 6
server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java

@@ -45,6 +45,7 @@ import password.pwm.http.bean.DeleteAccountBean;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.operations.ActionExecutor;
@@ -178,13 +179,13 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         if ( !deleteAccountBean.isAgreementPassed() )
         {
             deleteAccountBean.setAgreementPassed( true );
-            final AuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+            final AuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                     AuditEvent.AGREEMENT_PASSED,
                     pwmRequest.getUserInfoIfLoggedIn(),
                     pwmRequest.getLabel(),
                     ProfileDefinition.DeleteAccount.toString()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest,  auditRecord );
         }
 
         return ProcessStatus.Continue;
@@ -194,7 +195,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
     private ProcessStatus handleDeleteRequest(
             final PwmRequest pwmRequest
     )
-            throws ServletException, IOException, PwmUnrecoverableException, ChaiUnavailableException
+            throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final DeleteAccountProfile deleteAccountProfile = getProfile( pwmRequest );
@@ -208,7 +209,6 @@ public class DeleteAccountServlet extends ControlledPwmServlet
             {
                 LOGGER.debug( pwmRequest, () -> "executing configured actions to user " + userIdentity );
 
-
                 final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmDomain, userIdentity )
                         .setExpandPwmMacros( true )
                         .setMacroMachine( pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) )
@@ -230,7 +230,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         sendProfileUpdateEmailNotice( pwmRequest );
 
         // mark the event log
-        pwmDomain.getAuditManager().submit( AuditEvent.DELETE_ACCOUNT, pwmRequest.getPwmSession().getUserInfo(), pwmRequest.getPwmSession() );
+        AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.DELETE_ACCOUNT, pwmRequest.getPwmSession().getUserInfo() );
 
         final String nextUrl = deleteAccountProfile.readSettingAsString( PwmSetting.DELETE_ACCOUNT_NEXT_URL );
         if ( nextUrl != null && !nextUrl.isEmpty() )
@@ -244,7 +244,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
         // perform ldap entry delete.
         if ( deleteAccountProfile.readSettingAsBoolean( PwmSetting.DELETE_ACCOUNT_DELETE_USER_ENTRY ) )
         {
-            final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( pwmRequest.getUserInfoIfLoggedIn() );
+            final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn() );
             try
             {
                 chaiUser.getChaiProvider().deleteEntry( chaiUser.getEntryDN() );

+ 11 - 10
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -44,6 +44,7 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.CaptchaUtility;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
@@ -162,7 +163,7 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
                     forgottenUsernameForm, ssBean.getLocale() );
 
             // check for intruder search
-            pwmDomain.getIntruderManager().convenience().checkAttributes( formValues );
+            pwmDomain.getIntruderService().client().checkAttributes( formValues );
 
             // see if the values meet the configured form requirements.
             FormUtility.validateFormValues( pwmRequest.getDomainConfig(), formValues, ssBean.getLocale() );
@@ -195,15 +196,15 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
 
             if ( userIdentity == null )
             {
-                pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
-                pwmDomain.getStatisticsManager().incrementValue( Statistic.FORGOTTEN_USERNAME_FAILURES );
+                pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.FORGOTTEN_USERNAME_FAILURES );
                 setLastError( pwmRequest, PwmError.ERROR_CANT_MATCH_USER.toInfo() );
                 forwardToFormJsp( pwmRequest );
                 return;
             }
 
             // make sure the user isn't locked.
-            pwmDomain.getIntruderManager().convenience().checkUserIdentity( userIdentity );
+            pwmDomain.getIntruderService().client().checkUserIdentity( userIdentity );
 
             final UserInfo forgottenUserInfo = UserInfoFactory.newUserInfoUsingProxy(
                     pwmRequest.getPwmApplication(),
@@ -214,10 +215,10 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
             // send username
             sendUsername( pwmDomain, pwmRequest, forgottenUserInfo );
 
-            pwmDomain.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
-            pwmDomain.getIntruderManager().convenience().clearAttributes( formValues );
+            pwmDomain.getIntruderService().client().clearAddressAndSession( pwmSession );
+            pwmDomain.getIntruderService().client().clearAttributes( formValues );
 
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.FORGOTTEN_USERNAME_SUCCESSES );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.FORGOTTEN_USERNAME_SUCCESSES );
 
             // redirect user to success page.
             forwardToCompletePage( pwmRequest, userIdentity );
@@ -232,11 +233,11 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
                     e.getErrorInformation().getFieldValues() )
                     : e.getErrorInformation();
             setLastError( pwmRequest, errorInfo );
-            pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
-            pwmDomain.getIntruderManager().convenience().markAttributes( formValues, pwmRequest.getLabel() );
+            pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
+            pwmDomain.getIntruderService().client().markAttributes( formValues, pwmRequest.getLabel() );
         }
 
-        pwmDomain.getStatisticsManager().incrementValue( Statistic.FORGOTTEN_USERNAME_FAILURES );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.FORGOTTEN_USERNAME_FAILURES );
         forwardToFormJsp( pwmRequest );
     }
 

+ 6 - 4
server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -25,8 +25,8 @@ import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.UserIdentity;
@@ -52,6 +52,7 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.FormMap;
 import password.pwm.util.PasswordData;
 import password.pwm.util.form.FormUtility;
@@ -237,6 +238,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
 
             // check unique fields against ldap
             FormUtility.validateFormValueUniqueness(
+                    pwmRequest.getLabel(),
                     pwmDomain,
                     formValues,
                     ssBean.getLocale(),
@@ -264,7 +266,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
             );
             this.sendUpdateGuestEmailConfirmation( pwmRequest, guestUserInfoBean );
 
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.UPDATED_GUESTS );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.UPDATED_GUESTS );
 
             //everything good so forward to confirmation page.
             pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UpdateGuest );
@@ -508,7 +510,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
             //everything good so forward to success page.
             this.sendGuestUserEmailConfirmation( pwmRequest, userIdentity );
 
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.NEW_USERS );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.NEW_USERS );
 
             pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_CreateGuest );
         }
@@ -530,7 +532,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
     private static Instant readExpirationFromRequest(
             final PwmRequest pwmRequest
     )
-            throws PwmOperationalException, ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException
+            throws PwmOperationalException, ChaiOperationException, PwmUnrecoverableException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final DomainConfig config = pwmDomain.getConfig();

+ 9 - 10
server/src/main/java/password/pwm/http/servlet/SetupOtpServlet.java

@@ -24,8 +24,8 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import net.glxn.qrgen.QRCode;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -45,17 +45,18 @@ import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.SetupOtpBean;
 import password.pwm.ldap.auth.AuthenticationType;
-import password.pwm.svc.PwmService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.UserAuditRecord;
+import password.pwm.svc.otp.OTPUserRecord;
+import password.pwm.svc.otp.OtpService;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.Validator;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.operations.OtpService;
-import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
@@ -191,18 +192,16 @@ public class SetupOtpServlet extends ControlledPwmServlet
                 pwmSession.reloadUserInfoBean( pwmRequest );
 
                 // mark the event log
-                final UserAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+                final UserAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                         AuditEvent.SET_OTP_SECRET,
                         pwmSession.getUserInfo(),
                         pwmSession
                 );
-                pwmDomain.getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
 
+                AuditServiceClient.submit( pwmRequest, auditRecord );
+
+                StatisticsClient.incrementStat( pwmRequest, Statistic.SETUP_OTP_SECRET );
 
-                if ( pwmDomain.getStatisticsManager() != null && pwmDomain.getStatisticsManager().status() == PwmService.STATUS.OPEN )
-                {
-                    pwmDomain.getStatisticsManager().incrementValue( Statistic.SETUP_OTP_SECRET );
-                }
             }
             catch ( final Exception e )
             {

+ 9 - 5
server/src/main/java/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -32,8 +32,8 @@ import com.novell.ldapchai.exception.ChaiValidationException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import lombok.Value;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.ResponseInfoBean;
 import password.pwm.config.PwmSetting;
@@ -55,8 +55,10 @@ import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.UserAuditRecord;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -218,12 +220,13 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
 
             // mark the event log
-            final UserAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+            final UserAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                     AuditEvent.CLEAR_RESPONSES,
                     pwmSession.getUserInfo(),
                     pwmSession
             );
-            pwmDomain.getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+            AuditServiceClient.submit( pwmRequest, auditRecord );
 
             pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.SetupResponses );
         }
@@ -437,8 +440,9 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         final String userGUID = pwmSession.getUserInfo().getUserGuid();
         pwmDomain.getCrService().writeResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, userGUID, responseInfoBean );
         pwmSession.reloadUserInfoBean( pwmRequest );
-        pwmDomain.getStatisticsManager().incrementValue( Statistic.SETUP_RESPONSES );
-        pwmDomain.getAuditManager().submit( AuditEvent.SET_RESPONSES, pwmSession.getUserInfo(), pwmSession );
+
+        StatisticsClient.incrementStat( pwmRequest, Statistic.SETUP_RESPONSES );
+        AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.SET_RESPONSES, pwmSession.getUserInfo() );
     }
 
     private static Map<Challenge, String> readResponsesFromHttpRequest(

+ 4 - 4
server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java

@@ -22,8 +22,8 @@ package password.pwm.http.servlet;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.util.StringHelper;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.data.ShortcutItem;
@@ -36,9 +36,10 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ShortcutsBean;
-import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.permission.UserPermissionType;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 
@@ -218,7 +219,6 @@ public class ShortcutServlet extends AbstractPwmServlet
             throws PwmUnrecoverableException,  IOException, ServletException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
 
         final String link = pwmRequest.readParameterAsString( "link" );
         final Map<String, ShortcutItem> visibleItems = shortcutsBean.getVisibleItems();
@@ -227,7 +227,7 @@ public class ShortcutServlet extends AbstractPwmServlet
         {
             final ShortcutItem item = visibleItems.get( link );
 
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.SHORTCUTS_SELECTED );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.SHORTCUTS_SELECTED );
             LOGGER.trace( pwmRequest, () -> "shortcut link selected: " + link + ", setting link for 'forwardURL' to " + item.getShortcutURI() );
             pwmSession.getSessionStateBean().setForwardURL( item.getShortcutURI().toString() );
 

+ 11 - 9
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java

@@ -54,6 +54,7 @@ import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
@@ -242,7 +243,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
                     ssBean.getLocale() );
 
             // check for intruders
-            pwmDomain.getIntruderManager().convenience().checkAttributes( formValues );
+            pwmDomain.getIntruderService().client().checkAttributes( formValues );
 
             // read the context attr
             final String contextParam = pwmRequest.readParameterAsString( PwmConstants.PARAM_CONTEXT );
@@ -272,13 +273,13 @@ public class ActivateUserServlet extends ControlledPwmServlet
             ActivateUserUtils.validateParamsAgainstLDAP( pwmRequest, formValues, userIdentity );
 
             ActivateUserUtils.initUserActivationBean( pwmRequest, userIdentity );
-            pwmDomain.getIntruderManager().convenience().clearAttributes( formValues );
-            pwmDomain.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
+            pwmDomain.getIntruderService().client().clearAttributes( formValues );
+            pwmDomain.getIntruderService().client().clearAddressAndSession( pwmSession );
         }
         catch ( final PwmOperationalException e )
         {
-            pwmDomain.getIntruderManager().convenience().markAttributes( formValues, pwmRequest.getLabel() );
-            pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
+            pwmDomain.getIntruderService().client().markAttributes( formValues, pwmRequest.getLabel() );
+            pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
             setLastError( pwmRequest, e.getErrorInformation() );
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
         }
@@ -383,13 +384,14 @@ public class ActivateUserServlet extends ControlledPwmServlet
         if ( !activateUserBean.isAgreementPassed() )
         {
             activateUserBean.setAgreementPassed( true );
-            final AuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+            final AuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                     AuditEvent.AGREEMENT_PASSED,
                     pwmRequest.getUserInfoIfLoggedIn(),
                     pwmRequest.getLabel(),
                     "ActivateUser"
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         return ProcessStatus.Continue;
@@ -476,8 +478,8 @@ public class ActivateUserServlet extends ControlledPwmServlet
         catch ( final PwmOperationalException e )
         {
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
-            pwmDomain.getIntruderManager().convenience().markUserIdentity( activateUserBean.getUserIdentity(), pwmRequest );
-            pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
+            pwmDomain.getIntruderService().client().markUserIdentity( activateUserBean.getUserIdentity(), pwmRequest );
+            pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
             pwmRequest.respondWithError( e.getErrorInformation() );
         }
     }

+ 6 - 5
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -53,7 +53,9 @@ import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
 import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.svc.event.AuditEvent;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -83,7 +85,7 @@ class ActivateUserUtils
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userIdentity );
+        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequest.getLabel(), userIdentity );
 
         final ActivateUserProfile activateUserProfile = ActivateUserServlet.activateUserProfile( pwmRequest );
 
@@ -129,12 +131,11 @@ class ActivateUserUtils
             pwmSession.getLoginInfoBean().getAuthFlags().add( AuthenticationType.AUTH_FROM_PUBLIC_MODULE );
             pwmSession.getLoginInfoBean().getLoginFlags().add( LoginInfoBean.LoginFlag.forcePwChange );
 
-
             // mark the event log
-            pwmDomain.getAuditManager().submit( AuditEvent.ACTIVATE_USER, pwmSession.getUserInfo(), pwmSession );
+            AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.ACTIVATE_USER, pwmSession.getUserInfo() );
 
             // update the stats bean
-            pwmDomain.getStatisticsManager().incrementValue( Statistic.ACTIVATED_USERS );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.ACTIVATED_USERS );
 
             // send email or sms
             sendPostActivationNotice( pwmRequest );
@@ -155,7 +156,7 @@ class ActivateUserUtils
             throws ChaiUnavailableException, PwmDataValidationException, PwmUnrecoverableException
     {
         final String searchFilter = figureLdapSearchFilter( pwmRequest );
-        final ChaiProvider chaiProvider = pwmRequest.getPwmDomain().getProxyChaiProvider( userIdentity.getLdapProfileID() );
+        final ChaiProvider chaiProvider = pwmRequest.getPwmDomain().getProxyChaiProvider( pwmRequest.getLabel(), userIdentity.getLdapProfileID() );
         final ChaiUser chaiUser = chaiProvider.getEntryFactory().newChaiUser( userIdentity.getUserDN() );
 
         for ( final Map.Entry<FormConfiguration, String> entry : formValues.entrySet() )

+ 10 - 10
server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java

@@ -53,13 +53,13 @@ import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.event.AuditEventType;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.intruder.IntruderRecordType;
+import password.pwm.svc.intruder.PublicIntruderRecord;
 import password.pwm.svc.pwnotify.PwNotifyService;
 import password.pwm.svc.pwnotify.PwNotifyStoredJobState;
 import password.pwm.svc.report.ReportCsvUtility;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.report.UserReportRecord;
-import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.db.DatabaseException;
+import password.pwm.svc.stats.StatisticsService;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.ClosableIterator;
 import password.pwm.util.java.JavaHelper;
@@ -108,7 +108,6 @@ import java.util.zip.ZipOutputStream;
 )
 public class AdminServlet extends ControlledPwmServlet
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( AdminServlet.class );
 
     public enum AdminAction implements AbstractPwmServlet.ProcessAction
@@ -205,7 +204,7 @@ public class AdminServlet extends ControlledPwmServlet
         final OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream();
         try
         {
-            pwmDomain.getAuditManager().outputVaultToCsv( outputStream, pwmRequest.getLocale(), true );
+            pwmDomain.getAuditService().outputVaultToCsv( outputStream, pwmRequest.getLocale(), true );
         }
         catch ( final Exception e )
         {
@@ -295,7 +294,7 @@ public class AdminServlet extends ControlledPwmServlet
         final OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream();
         try
         {
-            final StatisticsManager statsManager = pwmDomain.getStatisticsManager();
+            final StatisticsService statsManager = pwmDomain.getStatisticsManager();
             statsManager.outputStatsToCsv( outputStream, pwmRequest.getLocale(), true );
         }
         catch ( final Exception e )
@@ -472,7 +471,7 @@ public class AdminServlet extends ControlledPwmServlet
         final int max = readMaxParameter( pwmRequest, 100, 10 * 1000 );
         final AuditEventType auditDataType = AuditEventType.valueOf( pwmRequest.readParameterAsString( "type", AuditEventType.USER.name() ) );
         final ArrayList<AuditRecord> records = new ArrayList<>();
-        final Iterator<AuditRecord> iterator = pwmRequest.getPwmDomain().getAuditManager().readVault();
+        final Iterator<AuditRecord> iterator = pwmRequest.getPwmDomain().getAuditService().readVault();
 
         while (
                 iterator.hasNext()
@@ -516,16 +515,16 @@ public class AdminServlet extends ControlledPwmServlet
 
     @ActionHandler( action = "intruderData" )
     private ProcessStatus restIntruderDataHandler( final PwmRequest pwmRequest )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException
+            throws  PwmUnrecoverableException, IOException
     {
         final int max = readMaxParameter( pwmRequest, 1000, 10 * 1000 );
 
-        final TreeMap<String, Object> returnData = new TreeMap<>();
+        final TreeMap<String, List<PublicIntruderRecord>> returnData = new TreeMap<>();
         try
         {
             for ( final IntruderRecordType recordType : IntruderRecordType.values() )
             {
-                returnData.put( recordType.toString(), pwmRequest.getPwmDomain().getIntruderManager().getRecords( recordType, max ) );
+                returnData.put( recordType.toString(), pwmRequest.getPwmApplication().getIntruderSystemService().getRecords( recordType, max ) );
             }
         }
         catch ( final PwmException e )
@@ -744,7 +743,8 @@ public class AdminServlet extends ControlledPwmServlet
     }
 
     @ActionHandler( action = "readPwNotifyLog" )
-    public ProcessStatus restreadPwNotifyLog( final PwmRequest pwmRequest ) throws IOException, DatabaseException, PwmUnrecoverableException
+    public ProcessStatus restreadPwNotifyLog( final PwmRequest pwmRequest )
+            throws IOException
     {
         final PwNotifyService pwNotifyService = pwmRequest.getPwmDomain().getPwNotifyService();
 

+ 67 - 36
server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java

@@ -23,8 +23,10 @@ package password.pwm.http.servlet.admin;
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmAboutProperty;
-import password.pwm.PwmDomain;
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
+import password.pwm.bean.DomainID;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmUnrecoverableException;
@@ -58,6 +60,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -73,13 +76,31 @@ public class AppDashboardData implements Serializable
     private static final PwmLogger LOGGER = PwmLogger.forClass( AppDashboardData.class );
 
     @Value
-    public static class ServiceData implements Serializable
+    public static class ServiceData implements Serializable, Comparable<ServiceData>
     {
+        private static final Comparator<ServiceData> COMPARATOR = Comparator.comparing(
+                ServiceData::getDomainID,
+                Comparator.nullsLast( Comparator.naturalOrder() ) )
+                .thenComparing(
+                        ServiceData::getName,
+                        Comparator.nullsLast( Comparator.naturalOrder() ) )
+                .thenComparing(
+                        ServiceData::getStatus,
+                        Comparator.nullsLast( Comparator.naturalOrder() ) );
+
+        private String guid;
+        private DomainID domainID;
         private String name;
         private PwmService.STATUS status;
         private Collection<DataStorageMethod> storageMethod;
         private List<HealthRecord> health;
         private Map<String, String> debugData;
+
+        @Override
+        public int compareTo( final ServiceData otherServiceData )
+        {
+            return COMPARATOR.compare( this, otherServiceData );
+        }
     }
 
     @Value
@@ -133,7 +154,7 @@ public class AppDashboardData implements Serializable
 
         final AppDashboardDataBuilder builder = AppDashboardData.builder();
         builder.about( makeAboutData( pwmDomain, contextManager, locale ) );
-        builder.services( getServiceData( pwmDomain ) );
+        builder.services( makeServiceData( pwmDomain.getPwmApplication() ) );
         builder.localDbInfo( makeLocalDbInfo( pwmDomain, locale ) );
         builder.javaAbout( makeAboutJavaData( pwmDomain, locale ) );
 
@@ -171,7 +192,7 @@ public class AppDashboardData implements Serializable
         builder.sessionCount( pwmDomain.getSessionTrackService().sessionCount() );
         builder.requestsInProgress( pwmDomain.getPwmApplication().getActiveServletRequests().get() );
 
-        LOGGER.trace( () -> "AppDashboardData bean created in ", () -> TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( () -> "AppDashboardData bean created", () -> TimeDuration.fromCurrent( startTime ) );
         return builder.build();
     }
 
@@ -238,34 +259,44 @@ public class AppDashboardData implements Serializable
         ) );
     }
 
-    private static List<ServiceData> getServiceData( final PwmDomain pwmDomain )
+    public static List<ServiceData> makeServiceData( final PwmApplication pwmApplication )
+            throws PwmUnrecoverableException
     {
-        final Map<String, ServiceData> returnData = new TreeMap<>();
-        for ( final PwmService pwmService : pwmDomain.getPwmServices() )
+        final List<ServiceData> returnData = new ArrayList<>();
+        for ( final Map.Entry<DomainID, List<PwmService>> domainIDListEntry : pwmApplication.getAppAndDomainPwmServices().entrySet() )
         {
-            final PwmService.ServiceInfo serviceInfo = pwmService.serviceInfo();
-            final Collection<DataStorageMethod> storageMethods = serviceInfo == null
-                    ? Collections.emptyList()
-                    : serviceInfo.getStorageMethods() == null
-                    ? Collections.emptyList()
-                    : serviceInfo.getStorageMethods();
-
-            final Map<String, String> debugData = serviceInfo == null
-                    ? Collections.emptyMap()
-                    : serviceInfo.getDebugProperties() == null
-                    ? Collections.emptyMap()
-                    : serviceInfo.getDebugProperties();
-
-            returnData.put( pwmService.getClass().getSimpleName(), new ServiceData(
-                    pwmService.getClass().getSimpleName(),
-                    pwmService.status(),
-                    storageMethods,
-                    pwmService.healthCheck(),
-                    debugData
-            ) );
+            final DomainID domainID = domainIDListEntry.getKey();
+            for ( final PwmService pwmService : domainIDListEntry.getValue() )
+            {
+                final PwmService.ServiceInfo serviceInfo = pwmService.serviceInfo();
+                final Collection<DataStorageMethod> storageMethods = serviceInfo == null
+                        ? Collections.emptyList()
+                        : serviceInfo.getStorageMethods() == null
+                                ? Collections.emptyList()
+                                : serviceInfo.getStorageMethods();
+
+                final Map<String, String> debugData = serviceInfo == null
+                        ? Collections.emptyMap()
+                        : serviceInfo.getDebugProperties() == null
+                                ? Collections.emptyMap()
+                                : serviceInfo.getDebugProperties();
+
+                final String guid = pwmApplication.getSecureService().hash( domainID + pwmService.getClass().getSimpleName() );
+
+                returnData.add( new ServiceData(
+                        guid,
+                        domainID,
+                        pwmService.getClass().getSimpleName(),
+                        pwmService.status(),
+                        storageMethods,
+                        pwmService.healthCheck(),
+                        debugData
+                ) );
+            }
         }
 
-        return List.copyOf( returnData.values() );
+        Collections.sort( returnData );
+        return List.copyOf( returnData );
     }
 
     private static List<DisplayElement> makeLocalDbInfo( final PwmDomain pwmDomain, final Locale locale )
@@ -323,16 +354,16 @@ public class AppDashboardData implements Serializable
                 "sharedHistorySize",
                 DisplayElement.Type.number,
                 "Syslog Queue Size",
-                String.valueOf( pwmDomain.getAuditManager().syslogQueueSize() )
+                String.valueOf( pwmDomain.getAuditService().syslogQueueSize() )
         ) );
         localDbInfo.add( new DisplayElement(
                 "localAuditRecords",
                 DisplayElement.Type.number,
                 "Audit Records",
-                pwmDomain.getAuditManager().sizeToDebugString()
+                pwmDomain.getAuditService().sizeToDebugString()
         ) );
         {
-            final Instant eldestAuditRecord = pwmDomain.getAuditManager().eldestVaultRecord();
+            final Instant eldestAuditRecord = pwmDomain.getAuditService().eldestVaultRecord();
             final String display = eldestAuditRecord != null
                     ? TimeDuration.fromCurrent( eldestAuditRecord ).asLongString()
                     : notApplicable;
@@ -366,9 +397,9 @@ public class AppDashboardData implements Serializable
             final String display = pwmDomain.getPwmApplication().getLocalDB() == null
                     ? notApplicable
                     : pwmDomain.getPwmApplication().getLocalDB().getFileLocation() == null
-                    ? notApplicable
-                    : StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize(
-                    pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) );
+                            ? notApplicable
+                            : StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize(
+                                    pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) );
             localDbInfo.add( new DisplayElement(
                     "localDbSizeOnDisk",
                     DisplayElement.Type.string,
@@ -380,8 +411,8 @@ public class AppDashboardData implements Serializable
             final String display = pwmDomain.getPwmApplication().getLocalDB() == null
                     ? notApplicable
                     : pwmDomain.getPwmApplication().getLocalDB().getFileLocation() == null
-                    ? notApplicable
-                    : StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) );
+                            ? notApplicable
+                            : StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( pwmDomain.getPwmApplication().getLocalDB().getFileLocation() ) );
             localDbInfo.add( new DisplayElement(
                     "localDbFreeSpace",
                     DisplayElement.Type.string,

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java

@@ -70,7 +70,7 @@ public class UserDebugDataReader
 
         final Map<ProfileDefinition, String> profiles = UserDebugDataReader.profileMap( pwmDomain, sessionLabel, userIdentity );
 
-        final PwmPasswordPolicy ldapPasswordPolicy = PasswordUtility.readLdapPasswordPolicy( pwmDomain, pwmDomain.getProxiedChaiUser( userIdentity ) );
+        final PwmPasswordPolicy ldapPasswordPolicy = PasswordUtility.readLdapPasswordPolicy( pwmDomain, pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity ) );
 
         final PwmPasswordPolicy configPasswordPolicy = PasswordUtility.determineConfiguredPolicyProfileForUser(
                 pwmDomain,

+ 39 - 26
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -50,6 +50,7 @@ import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.AvgStatistic;
 import password.pwm.util.PasswordData;
 import password.pwm.util.form.FormUtility;
@@ -75,6 +76,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 
 /**
@@ -131,6 +133,11 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         return pwmRequest.getChangePasswordProfile( );
     }
 
+    static ChangePasswordBean getBean( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    {
+       return pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+    }
+
     @ActionHandler( action = "reset" )
     ProcessStatus processResetAction( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException
     {
@@ -150,7 +157,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     @ActionHandler( action = "warnResponse" )
     public ProcessStatus processWarnResponse( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException
     {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
 
         if ( pwmRequest.getPwmSession().getUserInfo().getPasswordStatus().isWarnPeriod() )
         {
@@ -181,7 +188,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     @ActionHandler( action = "change" )
     ProcessStatus processChangeAction( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException, ChaiUnavailableException
     {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
 
         final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo();
 
@@ -190,14 +197,17 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
             return ProcessStatus.Continue;
         }
 
-        final PasswordData password1 = pwmRequest.readParameterAsPassword( "password1" );
-        final PasswordData password2 = pwmRequest.readParameterAsPassword( "password2" );
+        final PasswordData password1 = pwmRequest.readParameterAsPassword( "password1" )
+                .orElseThrow( () -> new NoSuchElementException( "missing password1 field" ) );
+        final PasswordData password2 = pwmRequest.readParameterAsPassword( "password2" )
+                .orElseThrow( () -> new NoSuchElementException( "missing password2 field" ) );
 
         // check the password meets the requirements
         try
         {
             final ChaiUser theUser = pwmRequest.getPwmSession().getSessionManager().getActor( );
-            final PwmPasswordRuleValidator pwmPasswordRuleValidator = new PwmPasswordRuleValidator( pwmRequest.getPwmDomain(), userInfo.getPasswordPolicy() );
+            final PwmPasswordRuleValidator pwmPasswordRuleValidator = PwmPasswordRuleValidator.create(
+                    pwmRequest.getLabel(), pwmRequest.getPwmDomain(), userInfo.getPasswordPolicy() );
             final PasswordData oldPassword = pwmRequest.getPwmSession().getLoginInfoBean().getUserCurrentPassword();
             pwmPasswordRuleValidator.testPassword( password1, oldPassword, userInfo, theUser );
         }
@@ -235,19 +245,19 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     @ActionHandler( action = "agree" )
     ProcessStatus processAgreeAction( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException, ChaiUnavailableException
     {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
 
         LOGGER.debug( pwmRequest, () -> "user accepted password change agreement" );
         if ( !changePasswordBean.isAgreementPassed() )
         {
             changePasswordBean.setAgreementPassed( true );
-            final AuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+            final AuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                     AuditEvent.AGREEMENT_PASSED,
                     pwmRequest.getUserInfoIfLoggedIn(),
                     pwmRequest.getLabel(),
                     "ChangePassword"
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         return ProcessStatus.Continue;
@@ -257,15 +267,17 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     ProcessStatus processFormAction( final PwmRequest pwmRequest )
             throws ServletException, PwmUnrecoverableException, IOException, ChaiUnavailableException
     {
-        final ChangePasswordBean cpb = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
         final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
         final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo();
         final LoginInfoBean loginBean = pwmRequest.getPwmSession().getLoginInfoBean();
 
-        final PasswordData currentPassword = pwmRequest.readParameterAsPassword( "currentPassword" );
+        final PasswordData currentPassword = pwmRequest.readParameterAsPassword( "currentPassword" )
+                .orElseThrow( () -> new NoSuchElementException( "missing currentPassword field" ) );
+
 
         // check the current password
-        if ( cpb.isCurrentPasswordRequired() && loginBean.getUserCurrentPassword() != null )
+        if ( changePasswordBean.isCurrentPasswordRequired() && loginBean.getUserCurrentPassword() != null )
         {
             if ( currentPassword == null )
             {
@@ -284,13 +296,13 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
 
             if ( !passed )
             {
-                pwmRequest.getPwmDomain().getIntruderManager().convenience().markUserIdentity(
+                pwmRequest.getPwmDomain().getIntruderService().client().markUserIdentity(
                         userInfo.getUserIdentity(), pwmRequest.getLabel() );
                 LOGGER.debug( pwmRequest, () -> "failed password validation check: currentPassword value is incorrect" );
                 setLastError( pwmRequest, new ErrorInformation( PwmError.ERROR_BAD_CURRENT_PASSWORD ) );
                 return ProcessStatus.Continue;
             }
-            cpb.setCurrentPasswordPassed( true );
+            changePasswordBean.setCurrentPasswordPassed( true );
         }
 
         final List<FormConfiguration> formItem = getProfile( pwmRequest ).readSettingAsForm( PwmSetting.PASSWORD_REQUIRE_FORM );
@@ -304,12 +316,12 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
             ChangePasswordServletUtil.validateParamsAgainstLDAP( formValues, pwmRequest,
                     pwmRequest.getPwmSession().getSessionManager().getActor( ) );
 
-            cpb.setFormPassed( true );
+            changePasswordBean.setFormPassed( true );
         }
         catch ( final PwmOperationalException e )
         {
-            pwmRequest.getPwmDomain().getIntruderManager().convenience().markAddressAndSession( pwmRequest );
-            pwmRequest.getPwmDomain().getIntruderManager().convenience().markUserIdentity( userInfo.getUserIdentity(), pwmRequest.getLabel() );
+            pwmRequest.getPwmDomain().getIntruderService().client().markAddressAndSession( pwmRequest );
+            pwmRequest.getPwmDomain().getIntruderService().client().markUserIdentity( userInfo.getUserIdentity(), pwmRequest.getLabel() );
             LOGGER.debug( pwmRequest, e.getErrorInformation() );
             setLastError( pwmRequest, e.getErrorInformation() );
             return ProcessStatus.Continue;
@@ -321,7 +333,8 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     @ActionHandler( action = "checkProgress" )
     ProcessStatus processCheckProgressAction( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException
     {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
+
         final PasswordChangeProgressChecker.ProgressTracker progressTracker = changePasswordBean.getChangeProgressTracker();
         final PasswordChangeProgressChecker.PasswordChangeProgress passwordChangeProgress;
         if ( progressTracker == null )
@@ -349,8 +362,9 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     @ActionHandler( action = "complete" )
     public ProcessStatus processCompleteAction( final PwmRequest pwmRequest ) throws ServletException, PwmUnrecoverableException, IOException
     {
-        final ChangePasswordBean cpb = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
-        final PasswordChangeProgressChecker.ProgressTracker progressTracker = cpb.getChangeProgressTracker();
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
+
+        final PasswordChangeProgressChecker.ProgressTracker progressTracker = changePasswordBean.getChangeProgressTracker();
         boolean isComplete = true;
         if ( progressTracker != null )
         {
@@ -380,7 +394,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
                     LOGGER.error( pwmRequest, () -> "unable to update average password sync time statistic: " + e.getMessage() );
                 }
             }
-            cpb.setChangeProgressTracker( null );
+            changePasswordBean.setChangeProgressTracker( null );
             final Locale locale = pwmRequest.getLocale();
             final String completeMessage = getProfile( pwmRequest ).readSettingAsLocalizedString( PwmSetting.PASSWORD_COMPLETE_MESSAGE, locale );
 
@@ -405,7 +419,8 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     }
 
     @ActionHandler( action = "checkPassword" )
-    private ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
+    private ProcessStatus processCheckPasswordAction( final PwmRequest pwmRequest )
+            throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final RestCheckPasswordServer.JsonInput jsonInput = JsonUtil.deserialize(
                 pwmRequest.readRequestBodyAsString(),
@@ -414,8 +429,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
 
         final UserInfo userInfo = pwmRequest.getPwmSession().getUserInfo();
         final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
-                pwmRequest.getPwmDomain(),
-                pwmRequest.getLocale(),
+                pwmRequest.getPwmRequestContext(),
                 pwmRequest.getPwmSession().getSessionManager().getActor(),
                 userInfo,
                 pwmRequest.getPwmSession().getLoginInfoBean(),
@@ -453,7 +467,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final ChangePasswordBean changePasswordBean = pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
 
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ChangePasswordProfile changePasswordProfile = getProfile( pwmRequest );
@@ -509,8 +523,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
     private void forwardToWaitPage( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, ServletException, IOException
     {
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final ChangePasswordBean changePasswordBean = pwmDomain.getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
+        final ChangePasswordBean changePasswordBean = getBean( pwmRequest );
         final Instant maxCompleteTime = changePasswordBean.getChangePasswordMaxCompletion();
         pwmRequest.setAttribute(
                 PwmRequestAttribute.ChangePassword_MaxWaitSeconds,

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

@@ -45,6 +45,7 @@ import password.pwm.ldap.PasswordChangeProgressChecker;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.event.AuditEvent;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.util.PasswordData;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.password.PasswordUtility;
@@ -237,7 +238,7 @@ public class ChangePasswordServletUtil
         ChangePasswordServletUtil.sendChangePasswordEmailNotice( pwmRequest );
 
         // send audit event
-        pwmDomain.getAuditManager().submit( AuditEvent.CHANGE_PASSWORD, pwmSession.getUserInfo(), pwmSession );
+        AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.CHANGE_PASSWORD, pwmSession.getUserInfo() );
     }
 
     static boolean warnPageShouldBeShown(

+ 58 - 50
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -36,8 +36,9 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.profile.EmailServerProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
-import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigSearchMachine;
+import password.pwm.config.stored.ConfigurationCleaner;
+import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationModifier;
@@ -76,6 +77,7 @@ import password.pwm.ldap.LdapBrowser;
 import password.pwm.svc.email.EmailServer;
 import password.pwm.svc.email.EmailServerUtil;
 import password.pwm.svc.email.EmailService;
+import password.pwm.svc.sms.SmsQueueService;
 import password.pwm.util.PasswordData;
 import password.pwm.util.SampleDataGenerator;
 import password.pwm.util.java.JavaHelper;
@@ -85,10 +87,9 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.password.RandomPasswordGenerator;
-import password.pwm.util.queue.SmsQueueManager;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestRandomPasswordServer;
-import password.pwm.ws.server.rest.bean.HealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthData;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -98,7 +99,6 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -107,9 +107,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
 @WebServlet(
         name = "ConfigEditorServlet",
@@ -140,12 +137,13 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         executeSettingFunction( HttpMethod.POST ),
         setConfigurationPassword( HttpMethod.POST ),
         readChangeLog( HttpMethod.POST ),
+        readWarnings( HttpMethod.POST ),
         search( HttpMethod.POST ),
         cancelEditing( HttpMethod.POST ),
         uploadFile( HttpMethod.POST ),
         setOption( HttpMethod.POST ),
         menuTreeData( HttpMethod.POST ),
-        settingData( HttpMethod.GET ),
+        settingData( HttpMethod.POST ),
         testMacro( HttpMethod.POST ),
         browseLdap( HttpMethod.POST ),
         copyProfile( HttpMethod.POST ),
@@ -186,12 +184,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             configManagerBean.setStoredConfiguration( loadedConfig );
         }
 
-        return ProcessStatus.Continue;
-    }
-
-    @Override
-    protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
-    {
         final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
         final DomainManageMode mode = domainStateReader.getMode();
 
@@ -203,13 +195,21 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             }
             else
             {
-                final DomainID baseDomain = pwmRequest.getDomainID();
                 pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.ConfigEditor.servletUrl()
-                                + "/" + baseDomain.stringValue() );
+                        + "/" + DomainID.systemId().stringValue() );
             }
-            return;
+            return ProcessStatus.Halt;
         }
 
+        return ProcessStatus.Continue;
+    }
+
+    @Override
+    protected void nextStep( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
+    {
+        final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
+        final DomainManageMode mode = domainStateReader.getMode();
+
         {
             final String value;
             switch ( mode )
@@ -332,7 +332,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
         final ReadSettingResponse readSettingResponse;
 
-        final StoredConfiguration storedConfiguration;
         if ( settingKey.startsWith( "localeBundle" ) )
         {
             final StringTokenizer st = new StringTokenizer( settingKey, "-" );
@@ -345,8 +344,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
 
             final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle();
             modifier.writeLocaleBundleMap( domainID, pwmLocaleBundle, keyName, outputMap );
-            storedConfiguration = modifier.newStoredConfiguration();
-            readSettingResponse = ConfigEditorServletUtils.handleLocaleBundleReadSetting( pwmRequest, storedConfiguration, settingKey );
+            readSettingResponse = ConfigEditorServletUtils.handleLocaleBundleReadSetting( pwmRequest, modifier.newStoredConfiguration(), settingKey );
         }
         else
         {
@@ -359,15 +357,16 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             {
                 final StoredValue storedValue = ValueFactory.fromJson( setting, bodyString );
                 modifier.writeSetting( key, storedValue, loggedInUser );
-                storedConfiguration = modifier.newStoredConfiguration();
             }
             catch ( final PwmOperationalException e )
             {
                 throw new PwmUnrecoverableException( e.getErrorInformation() );
             }
-            readSettingResponse = ConfigEditorServletUtils.handleReadSetting( pwmRequest, storedConfiguration, settingKey );
+            readSettingResponse = ConfigEditorServletUtils.handleReadSetting( pwmRequest, modifier.newStoredConfiguration(), settingKey );
         }
-        configManagerBean.setStoredConfiguration( storedConfiguration );
+
+        ConfigurationCleaner.postProcessStoredConfig( modifier );
+        configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         pwmRequest.outputJsonResult( RestResultBean.withData( readSettingResponse ) );
         return ProcessStatus.Halt;
     }
@@ -403,6 +402,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             modifier.resetSetting( key, loggedInUser );
         }
 
+        ConfigurationCleaner.postProcessStoredConfig( modifier );
         configManagerBean.setStoredConfiguration( modifier.newStoredConfiguration() );
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );
         return ProcessStatus.Halt;
@@ -412,7 +412,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     private ProcessStatus restSetConfigurationPassword(
             final PwmRequest pwmRequest
     )
-            throws IOException, ServletException, PwmUnrecoverableException
+            throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( configManagerBean.getStoredConfiguration() );
@@ -545,19 +545,27 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
 
-        final Map<String, Object> returnObj = new ConcurrentHashMap<>();
-        final ExecutorService executor = Executors.newFixedThreadPool( 3 );
-        executor.execute( () -> ConfigEditorServletUtils.outputChangeLogData( pwmRequest, configManagerBean, returnObj ) );
-        executor.execute( () -> returnObj.put( "health", ConfigEditorServletUtils.configurationHealth( pwmRequest, configManagerBean ) ) );
-        JavaHelper.closeAndWaitExecutor( executor, TimeDuration.MINUTE );
-
-        final RestResultBean restResultBean = RestResultBean.withData( new HashMap<>( returnObj ) );
+        final Map<String, String> returnObj = ConfigEditorServletUtils.outputChangeLogData( pwmRequest, configManagerBean );
+        final RestResultBean restResultBean = RestResultBean.withData( new LinkedHashMap<>( returnObj ) );
         pwmRequest.outputJsonResult( restResultBean );
 
         return ProcessStatus.Halt;
     }
 
+    @ActionHandler( action = "readWarnings" )
+    private ProcessStatus restReadWarnings(
+            final PwmRequest pwmRequest
+    )
+            throws IOException, PwmUnrecoverableException
+    {
+        final ConfigManagerBean configManagerBean = getBean( pwmRequest );
 
+        final Map<DomainID, List<String>> healthData = ConfigEditorServletUtils.configurationHealth( pwmRequest, configManagerBean );
+        final RestResultBean restResultBean = RestResultBean.withData( new LinkedHashMap<>( healthData ) );
+        pwmRequest.outputJsonResult( restResultBean );
+
+        return ProcessStatus.Halt;
+    }
 
     @ActionHandler( action = "search" )
     private ProcessStatus restSearchSettings(
@@ -615,7 +623,14 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final String profileID = pwmRequest.readParameterAsString( "profile" );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
         final DomainConfig config = new AppConfig( configManagerBean.getStoredConfiguration() ).getDomainConfigs().get( domainID );
-        final HealthData healthData = LDAPHealthChecker.healthForNewConfiguration( pwmRequest.getPwmDomain(), config, pwmRequest.getLocale(), profileID, true, true );
+        final PublicHealthData healthData = LDAPHealthChecker.healthForNewConfiguration(
+                pwmRequest.getLabel(),
+                pwmRequest.getPwmDomain(),
+                config,
+                pwmRequest.getLocale(),
+                profileID,
+                true,
+                true );
         final RestResultBean restResultBean = RestResultBean.withData( healthData );
 
         pwmRequest.outputJsonResult( restResultBean );
@@ -635,7 +650,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final AppConfig config = new AppConfig( configManagerBean.getStoredConfiguration() );
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainID( PwmSetting.LDAP_SERVER_URLS );
         final List<HealthRecord> healthRecords = DatabaseStatusChecker.checkNewDatabaseStatus( pwmRequest.getPwmApplication(), config );
-        final HealthData healthData = HealthRecord.asHealthDataBean( config.getDomainConfigs().get( domainID ), pwmRequest.getLocale(), healthRecords );
+        final PublicHealthData healthData = HealthRecord.asHealthDataBean( config.getDomainConfigs().get( domainID ), pwmRequest.getLocale(), healthRecords );
         final RestResultBean restResultBean = RestResultBean.withData( healthData );
         pwmRequest.outputJsonResult( restResultBean );
         LOGGER.debug( pwmRequest, () -> "completed restDatabaseHealthCheck in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
@@ -657,7 +672,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final StringBuilder output = new StringBuilder();
         output.append( "beginning SMS send process:\n" );
 
-        if ( !SmsQueueManager.smsIsConfigured( config.getAppConfig() ) )
+        if ( !SmsQueueService.smsIsConfigured( config.getAppConfig() ) )
         {
             output.append( "SMS not configured." );
         }
@@ -667,7 +682,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             final SmsItemBean testSmsItem = new SmsItemBean( testParams.get( "to" ), testParams.get( "message" ), pwmRequest.getLabel() );
             try
             {
-                final String responseBody = SmsQueueManager.sendDirectMessage(
+                final String responseBody = SmsQueueService.sendDirectMessage(
                         pwmRequest.getPwmDomain(),
                         config,
                         pwmRequest.getLabel(),
@@ -795,19 +810,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final Instant startTime = Instant.now();
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
 
-        final Map<String, Object> inputParameters = pwmRequest.readBodyAsJsonMap( PwmHttpRequestWrapper.Flag.BypassValidation );
-        final boolean modifiedSettingsOnly = ( boolean ) inputParameters.get( "modifiedSettingsOnly" );
-        final int level = ( int ) ( ( double ) inputParameters.get( "level" ) );
-        final String filterText = ( String ) inputParameters.get( "text" );
-        final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
-
-        final NavTreeSettings navTreeSettings = NavTreeSettings.builder()
-                .modifiedSettingsOnly( modifiedSettingsOnly )
-                .domainManageMode( domainStateReader.getMode() )
-                .level( level )
-                .filterText( filterText )
-                .locale( pwmRequest.getLocale() )
-                .build();
+        final NavTreeSettings navTreeSettings = NavTreeSettings.readFromRequest( pwmRequest );
 
         final StoredConfiguration storedConfiguration = configManagerBean.getStoredConfiguration();
 
@@ -827,11 +830,15 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     {
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForLocaleBundle();
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final SettingData settingData =  SettingDataMaker.generateSettingData(
+
+        final NavTreeSettings navTreeSettings = NavTreeSettings.readFromRequest( pwmRequest );
+
+        final SettingData settingData = SettingDataMaker.generateSettingData(
                 domainID,
                 configManagerBean.getStoredConfiguration(),
                 pwmRequest.getLabel(),
-                pwmRequest.getLocale()
+                pwmRequest.getLocale(),
+                navTreeSettings
         );
 
         final RestResultBean restResultBean = RestResultBean.withData( settingData );
@@ -876,6 +883,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final DomainID domainID = DomainStateReader.forRequest( pwmRequest ).getDomainIDForDomainSetting(  );
 
         final LdapBrowser ldapBrowser = new LdapBrowser(
+                pwmRequest.getLabel(),
                 pwmRequest.getPwmDomain().getLdapConnectionService().getChaiProviderFactory(),
                 configManagerBean.getStoredConfiguration()
         );

+ 21 - 45
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java

@@ -23,7 +23,6 @@ package password.pwm.http.servlet.configeditor;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
-import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
@@ -46,32 +45,30 @@ import password.pwm.health.ConfigurationChecker;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.ConfigManagerBean;
-import password.pwm.http.servlet.configguide.ConfigGuideForm;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.CollectionUtil;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.ws.server.RestResultBean;
-import password.pwm.ws.server.rest.bean.HealthData;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.Set;
 import java.util.StringTokenizer;
-import java.util.stream.Collectors;
+import java.util.TreeMap;
 
 public class ConfigEditorServletUtils
 {
@@ -83,7 +80,6 @@ public class ConfigEditorServletUtils
     )
             throws PwmUnrecoverableException, IOException
     {
-
         final Map<String, PwmRequest.FileUploadItem> fileUploads;
         try
         {
@@ -115,47 +111,26 @@ public class ConfigEditorServletUtils
         return Optional.empty();
     }
 
-    static void outputChangeLogData(
+    static Map<String, String> outputChangeLogData(
             final PwmRequest pwmRequest,
-            final ConfigManagerBean configManagerBean,
-            final Map<String, Object> outputMap
+            final ConfigManagerBean configManagerBean
     )
     {
         final Locale locale = pwmRequest.getLocale();
 
-        final Set<StoredConfigKey> changeLog = StoredConfigurationUtil.changedValues(
+        final Set<StoredConfigKey> changedKeys = StoredConfigurationUtil.changedValues(
                 pwmRequest.getPwmDomain().getConfig().getStoredConfiguration(),
                 configManagerBean.getStoredConfiguration() );
 
-        final List<StoredConfigKey> keys = CollectionUtil.iteratorToStream(  configManagerBean.getStoredConfiguration().keys() ).collect( Collectors.toList() );
-
         final Map<String, String> changeLogMap = StoredConfigurationUtil.makeDebugMap(
                 configManagerBean.getStoredConfiguration(),
-                keys,
+                changedKeys,
                 locale );
 
-        final StringBuilder output = new StringBuilder();
-        if ( changeLogMap.isEmpty() )
-        {
-            output.append( "No setting changes." );
-        }
-        else
-        {
-            for ( final Map.Entry<String, String> entry : changeLogMap.entrySet() )
-            {
-                output.append( "<div class=\"changeLogKey\">" );
-                output.append( entry.getKey() );
-                output.append( "</div><div class=\"changeLogValue\">" );
-                output.append( StringUtil.escapeHtml( entry.getValue() ) );
-                output.append( "</div>" );
-            }
-        }
-        outputMap.put( "html", output.toString() );
-        outputMap.put( "modified", !changeLog.isEmpty() );
-
+        return changeLogMap;
     }
 
-    static HealthData configurationHealth(
+    static Map<DomainID, List<String>> configurationHealth(
             final PwmRequest pwmRequest,
             final ConfigManagerBean configManagerBean
     )
@@ -169,26 +144,25 @@ public class ConfigEditorServletUtils
             final PwmApplication tempApplication = PwmApplication.createPwmApplication( pwmRequest.getPwmApplication()
                     .getPwmEnvironment()
                     .makeRuntimeInstance( new AppConfig( configManagerBean.getStoredConfiguration() ) ) );
-            final PwmDomain tempDomain = tempApplication.domains().get( ConfigGuideForm.DOMAIN_ID );
 
-            final List<HealthRecord> healthRecords = configurationChecker.doHealthCheck(
-                    new AppConfig( configManagerBean.getStoredConfiguration() ),
-                    pwmRequest.getLocale()
-            );
+            final List<HealthRecord> healthRecords = configurationChecker.doHealthCheck( tempApplication, pwmRequest.getLabel() );
+            final Map<DomainID, List<String>> returnData = new TreeMap<>();
+
+            healthRecords.forEach( record ->
+                    returnData.computeIfAbsent(
+                            record.getDomainID(), k -> new ArrayList<>() )
+                            .add( record.getDetail( locale, pwmRequest.getAppConfig() ) ) );
 
             LOGGER.debug( () -> "config health check done in ", () -> TimeDuration.fromCurrent( startTime ) );
 
-            return HealthData.builder()
-                    .overall( "CONFIG" )
-                    .records( password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( healthRecords, locale, tempDomain.getConfig() ) )
-                    .build();
+            return Collections.unmodifiableMap( returnData );
         }
         catch ( final Exception e )
         {
             LOGGER.error( pwmRequest, () -> "error generating health records: " + e.getMessage() );
         }
 
-        return HealthData.builder().build();
+        return Collections.emptyMap();
     }
 
     static ConfigEditorServlet.ReadSettingResponse handleLocaleBundleReadSetting(
@@ -318,7 +292,9 @@ public class ConfigEditorServletUtils
     {
         try
         {
-            final PasswordData passwordData = pwmRequest.readParameterAsPassword( "password" );
+            final PasswordData passwordData = pwmRequest.readParameterAsPassword( "password" )
+                    .orElseThrow( () -> new NoSuchElementException( "missing 'password' field" ) );
+
             final String alias = pwmRequest.readParameterAsString( "alias" );
             final HttpsServerCertificateManager.KeyStoreFormat keyStoreFormat;
             try

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/configeditor/DomainStateReader.java

@@ -34,7 +34,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
-class DomainStateReader
+public class DomainStateReader
 {
     private final PwmRequest pwmRequest;
 

+ 18 - 5
server/src/main/java/password/pwm/http/servlet/configeditor/data/NavTreeDataMaker.java

@@ -27,7 +27,9 @@ import password.pwm.bean.DomainID;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.PwmSettingFlag;
 import password.pwm.config.PwmSettingScope;
+import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.stored.ConfigSearchMachine;
 import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
@@ -327,7 +329,7 @@ public class NavTreeDataMaker
 
         for ( final PwmSetting setting : category.getSettings() )
         {
-            if ( settingMatcher( pwmDomain, storedConfiguration, setting, profile, navTreeSettings ) )
+            if ( settingMatcher( pwmDomain.getDomainID(), storedConfiguration, setting, profile, navTreeSettings ) )
             {
                 return true;
             }
@@ -336,17 +338,22 @@ public class NavTreeDataMaker
         return false;
     }
 
-    private static boolean settingMatcher(
-            final PwmDomain pwmDomain,
+    static boolean settingMatcher(
+            final DomainID domainID,
             final StoredConfiguration storedConfiguration,
             final PwmSetting setting,
             final String profileID,
             final NavTreeSettings navTreeSettings
     )
     {
-        final StoredConfigKey storedConfigKey = StoredConfigKey.forSetting( setting, profileID, pwmDomain.getDomainID() );
-        final boolean valueIsDefault = StoredConfigurationUtil.isDefaultValue( storedConfiguration, storedConfigKey );
+        final StoredConfigKey storedConfigKey = StoredConfigKey.forSetting( setting, profileID, domainID );
+
+        if ( setting.getSyntax() == PwmSettingSyntax.PROFILE && !setting.isHidden() && setting.getCategory().getParent().isHidden() )
+        {
+            return true;
+        }
 
+        final boolean valueIsDefault = StoredConfigurationUtil.isDefaultValue( storedConfiguration, storedConfigKey );
         if ( setting.isHidden() && !valueIsDefault )
         {
             return false;
@@ -375,6 +382,12 @@ public class NavTreeDataMaker
             return false;
         }
 
+        if ( setting.getFlags().contains( PwmSettingFlag.MultiDomain )
+                && ( !( new AppConfig( storedConfiguration ).isMultiDomain() ) ) )
+        {
+            return false;
+        }
+
         if ( StringUtil.isEmpty( navTreeSettings.getFilterText() ) )
         {
             return true;

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

@@ -23,10 +23,16 @@ package password.pwm.http.servlet.configeditor.data;
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmConstants;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmHttpRequestWrapper;
+import password.pwm.http.PwmRequest;
 import password.pwm.http.servlet.configeditor.DomainManageMode;
+import password.pwm.http.servlet.configeditor.DomainStateReader;
 
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.Locale;
+import java.util.Map;
 
 @Value
 @Builder
@@ -43,4 +49,28 @@ public class NavTreeSettings implements Serializable
     private final Locale locale = PwmConstants.DEFAULT_LOCALE;
 
     private final DomainManageMode domainManageMode;
+
+    public static NavTreeSettings forBasic()
+    {
+        return NavTreeSettings.builder()
+                .domainManageMode( DomainManageMode.system )
+                .build();
+    }
+
+    public static NavTreeSettings readFromRequest( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException
+    {
+        final Map<String, Object> inputParameters = pwmRequest.readBodyAsJsonMap( PwmHttpRequestWrapper.Flag.BypassValidation );
+        final boolean modifiedSettingsOnly = ( boolean ) inputParameters.get( "modifiedSettingsOnly" );
+        final int level = ( int ) ( ( double ) inputParameters.get( "level" ) );
+        final String filterText = ( String ) inputParameters.get( "text" );
+        final DomainStateReader domainStateReader = DomainStateReader.forRequest( pwmRequest );
+
+        return NavTreeSettings.builder()
+                .modifiedSettingsOnly( modifiedSettingsOnly )
+                .domainManageMode( domainStateReader.getMode() )
+                .level( level )
+                .filterText( filterText )
+                .locale( pwmRequest.getLocale() )
+                .build();
+    }
 }

+ 23 - 7
server/src/main/java/password/pwm/http/servlet/configeditor/data/SettingDataMaker.java

@@ -25,6 +25,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingTemplateSet;
+import password.pwm.config.stored.StoredConfigKey;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.PwmUnrecoverableException;
@@ -38,6 +39,7 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 public class SettingDataMaker
@@ -48,19 +50,33 @@ public class SettingDataMaker
             final DomainID domainID,
             final StoredConfiguration storedConfiguration,
             final SessionLabel sessionLabel,
-            final Locale locale
+            final Locale locale,
+            final NavTreeSettings navTreeSettings
     )
             throws PwmUnrecoverableException
     {
         final Instant startGenerateTime = Instant.now();
         final PwmSettingTemplateSet templateSet = storedConfiguration.getTemplateSet().get( domainID );
 
-        final Map<String, SettingInfo> settingMap = Collections.unmodifiableMap( Arrays.stream( PwmSetting.values() )
-                .collect( Collectors.toMap(
-                        PwmSetting::getKey,
-                        pwmSetting -> SettingInfo.forSetting( pwmSetting, templateSet, locale ),
-                        ( u, v ) -> v,
-                        LinkedHashMap::new ) ) );
+        final Map<String, SettingInfo> settingMap;
+        {
+            final Set<PwmSetting> interestedSets = StoredConfigurationUtil.allPossibleSettingKeysForConfiguration( storedConfiguration ).stream()
+                    .filter( k -> k.isRecordType( StoredConfigKey.RecordType.SETTING ) )
+                    .filter( k -> NavTreeDataMaker.settingMatcher( domainID, storedConfiguration, k.toPwmSetting(), k.getProfileID(), navTreeSettings ) )
+                    .map( StoredConfigKey::toPwmSetting )
+                    .collect( Collectors.toSet() );
+
+            settingMap = interestedSets.stream()
+                    .sorted()
+                    .collect( Collectors.toMap(
+                            PwmSetting::getKey,
+                            pwmSetting -> SettingInfo.forSetting( pwmSetting, templateSet, locale ),
+                            ( u, v ) ->
+                            {
+                                throw new IllegalStateException();
+                            },
+                            LinkedHashMap::new ) );
+        }
 
         final Map<String, CategoryInfo> categoryInfoMap = Collections.unmodifiableMap( Arrays.stream( PwmSettingCategory.values() )
                 .collect( Collectors.toMap(

+ 16 - 10
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -28,6 +28,7 @@ import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.AppConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
@@ -60,6 +61,7 @@ import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.configeditor.ConfigEditorServletUtils;
+import password.pwm.http.servlet.configeditor.data.NavTreeSettings;
 import password.pwm.http.servlet.configeditor.data.SettingData;
 import password.pwm.http.servlet.configeditor.data.SettingDataMaker;
 import password.pwm.i18n.Message;
@@ -71,7 +73,8 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.X509Utils;
 import password.pwm.ws.server.RestResultBean;
-import password.pwm.ws.server.rest.bean.HealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthData;
+import password.pwm.ws.server.rest.bean.PublicHealthRecord;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
@@ -240,12 +243,13 @@ public class ConfigGuideServlet extends ControlledPwmServlet
                 .getPwmEnvironment()
                 .makeRuntimeInstance( tempAppConfig ) );
         final PwmDomain tempDomain = tempApplication.domains().get( ConfigGuideForm.DOMAIN_ID );
-        // final DomainConfig tempDomainConfig = tempDomain.getConfig();
 
         final LDAPHealthChecker ldapHealthChecker = new LDAPHealthChecker();
         final List<HealthRecord> records = new ArrayList<>();
         final LdapProfile ldapProfile = tempDomain.getConfig().getDefaultLdapProfile();
 
+        final SessionLabel sessionLabel = pwmRequest.getLabel();
+
         switch ( configGuideBean.getStep() )
         {
             case LDAP_SERVER:
@@ -271,7 +275,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
             case LDAP_PROXY:
             {
-                records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( tempDomain, tempDomain.getConfig(), ldapProfile, false ) );
+                records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( sessionLabel, tempDomain, tempDomain.getConfig(), ldapProfile, false ) );
                 if ( records.isEmpty() )
                 {
                     records.add( password.pwm.health.HealthRecord.forMessage( ConfigGuideForm.DOMAIN_ID, HealthMessage.LDAP_OK ) );
@@ -281,7 +285,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
             case LDAP_CONTEXT:
             {
-                records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( tempDomain, tempDomain.getConfig(), ldapProfile, true ) );
+                records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( sessionLabel, tempDomain, tempDomain.getConfig(), ldapProfile, true ) );
                 if ( records.isEmpty() )
                 {
                     records.add( HealthRecord.forMessage(
@@ -303,8 +307,8 @@ public class ConfigGuideServlet extends ControlledPwmServlet
                 final String testUserValue = configGuideBean.getFormData().get( ConfigGuideFormField.PARAM_LDAP_TEST_USER );
                 if ( testUserValue != null && !testUserValue.isEmpty() )
                 {
-                    records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( tempDomain, tempDomain.getConfig(), ldapProfile, false ) );
-                    records.addAll( ldapHealthChecker.doLdapTestUserCheck( tempDomain.getConfig(), ldapProfile, tempDomain ) );
+                    records.addAll( ldapHealthChecker.checkBasicLdapConnectivity( sessionLabel, tempDomain, tempDomain.getConfig(), ldapProfile, false ) );
+                    records.addAll( ldapHealthChecker.doLdapTestUserCheck( sessionLabel, tempDomain.getConfig(), ldapProfile, tempDomain ) );
                 }
                 else
                 {
@@ -327,8 +331,8 @@ public class ConfigGuideServlet extends ControlledPwmServlet
                 JavaHelper.unhandledSwitchStatement( configGuideBean.getStep() );
         }
 
-        final HealthData jsonOutput = HealthData.builder()
-                .records( password.pwm.ws.server.rest.bean.HealthRecord.fromHealthRecords( records, pwmRequest.getLocale(), tempDomain.getConfig() ) )
+        final PublicHealthData jsonOutput = PublicHealthData.builder()
+                .records( PublicHealthRecord.fromHealthRecords( records, pwmRequest.getLocale(), tempDomain.getConfig() ) )
                 .timestamp( Instant.now() )
                 .overall( HealthUtils.getMostSevereHealthStatus( records ).toString() )
                 .build();
@@ -361,6 +365,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final String dn = inputMap.getOrDefault( "dn", "" );
 
         final LdapBrowser ldapBrowser = new LdapBrowser(
+                pwmRequest.getLabel(),
                 pwmRequest.getPwmDomain().getLdapConnectionService().getChaiProviderFactory(),
                 storedConfiguration
         );
@@ -612,7 +617,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         }
         catch ( final Exception e )
         {
-            final String errorMsg = "error writing default value for setting " + pwmSetting.toString() + ", error: " + e.getMessage();
+            final String errorMsg = "error writing default value for setting " + pwmSetting + ", error: " + e.getMessage();
             LOGGER.error( () -> errorMsg, e );
             throw new IllegalStateException( errorMsg, e );
         }
@@ -637,7 +642,8 @@ public class ConfigGuideServlet extends ControlledPwmServlet
                 ConfigGuideForm.DOMAIN_ID,
                 storedConfiguration,
                 pwmRequest.getLabel(),
-                pwmRequest.getLocale()
+                pwmRequest.getLocale(),
+                NavTreeSettings.forBasic()
         );
 
         final RestResultBean restResultBean = RestResultBean.withData( settingData );

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -77,7 +77,6 @@ import java.util.Optional;
 
 public class ConfigGuideUtils
 {
-
     private static final PwmLogger LOGGER = PwmLogger.getLogger( ConfigGuideUtils.class.getName() );
 
     static void writeConfig(
@@ -196,7 +195,7 @@ public class ConfigGuideUtils
     {
         final int ordinal = step.ordinal();
         final int total = GuideStep.values().length - 2;
-        return new Percent( ordinal, total );
+        return Percent.of( ordinal, total );
     }
 
     static void checkLdapServer( final ConfigGuideBean configGuideBean )
@@ -294,6 +293,7 @@ public class ConfigGuideUtils
 
             final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
             final Collection<UserIdentity> results = userMatchViewerFunction.discoverMatchingUsers(
+                    pwmRequest.getLabel(),
                     pwmDomain,
                     1,
                     storedConfiguration,
@@ -302,7 +302,7 @@ public class ConfigGuideUtils
             if ( !results.isEmpty() )
             {
                 final UserIdentity foundIdentity = results.iterator().next();
-                if ( foundIdentity.canonicalEquals( adminIdentity, tempApplication ) )
+                if ( foundIdentity.canonicalEquals( pwmRequest.getLabel(), adminIdentity, tempApplication ) )
                 {
                     records.add( HealthRecord.forMessage( ConfigGuideForm.DOMAIN_ID, HealthMessage.LDAP_AdminUserOk ) );
                 }

+ 4 - 4
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java

@@ -150,8 +150,8 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
             else
             {
                 LOGGER.trace( pwmRequest, () -> "configuration password is not correct" );
-                pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
-                pwmDomain.getIntruderManager().mark( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmRequest.getLabel() );
+                pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
+                pwmDomain.getIntruderService().mark( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmRequest.getLabel() );
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_PASSWORD_ONLY_BAD );
                 updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), false );
                 setLastError( pwmRequest, errorInformation );
@@ -257,8 +257,8 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
         final PwmSession pwmSession = pwmRequest.getPwmSession();
 
         configManagerBean.setPasswordVerified( true );
-        pwmDomain.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
-        pwmDomain.getIntruderManager().clear( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
+        pwmDomain.getIntruderService().client().clearAddressAndSession( pwmSession );
+        pwmDomain.getIntruderService().clear( IntruderRecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
         pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded( true );
         if ( persistentLoginEnabled && "on".equals( pwmRequest.readParameterAsString( "remember" ) ) )
         {

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

@@ -72,8 +72,9 @@ import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
@@ -86,8 +87,8 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
-import password.pwm.util.operations.cr.NMASCrOperator;
-import password.pwm.util.operations.otp.OTPUserRecord;
+import password.pwm.svc.cr.NMASCrOperator;
+import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.ws.server.RestResultBean;
 
@@ -197,7 +198,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
         if ( forgottenPasswordBean.getUserIdentity() != null )
         {
-            pwmDomain.getIntruderManager().convenience().checkUserIdentity( forgottenPasswordBean.getUserIdentity() );
+            pwmDomain.getIntruderService().client().checkUserIdentity( forgottenPasswordBean.getUserIdentity() );
         }
 
         checkForLocaleSwitch( pwmRequest, forgottenPasswordBean );
@@ -423,7 +424,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             formValues = FormUtility.readFormValuesFromRequest( pwmRequest, forgottenPasswordForm, userLocale );
 
             // check for intruder search values
-            pwmDomain.getIntruderManager().convenience().checkAttributes( formValues );
+            pwmDomain.getIntruderService().client().checkAttributes( formValues );
 
             // see if the values meet the configured form requirements.
             FormUtility.validateFormValues( pwmRequest.getDomainConfig(), formValues, userLocale );
@@ -461,13 +462,13 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_CANT_MATCH_USER ) );
             }
 
-            AuthenticationUtility.checkIfUserEligibleToAuthentication( pwmDomain, userIdentity );
+            AuthenticationUtility.checkIfUserEligibleToAuthentication( pwmRequest.getLabel(), pwmDomain, userIdentity );
 
             final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
             ForgottenPasswordUtil.initForgottenPasswordBean( pwmRequest.getPwmRequestContext(), userIdentity, forgottenPasswordBean );
 
             // clear intruder search values
-            pwmDomain.getIntruderManager().convenience().clearAttributes( formValues );
+            pwmDomain.getIntruderService().client().clearAttributes( formValues );
 
             return ProcessStatus.Continue;
         }
@@ -479,10 +480,11 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                         PwmError.ERROR_RESPONSES_NORESPONSES,
                         e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues()
                 );
-                pwmDomain.getStatisticsManager().incrementValue( Statistic.RECOVERY_FAILURES );
 
-                pwmDomain.getIntruderManager().convenience().markAddressAndSession( pwmRequest );
-                pwmDomain.getIntruderManager().convenience().markAttributes( formValues, pwmRequest.getLabel() );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_FAILURES );
+
+                pwmDomain.getIntruderService().client().markAddressAndSession( pwmRequest );
+                pwmDomain.getIntruderService().client().markAttributes( formValues, pwmRequest.getLabel() );
 
                 LOGGER.debug( pwmRequest, errorInfo );
                 setLastError( pwmRequest, errorInfo );
@@ -529,7 +531,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 );
             }
             forgottenPasswordBean.getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.TOKEN );
-            StatisticsManager.incrementStat( pwmRequest.getPwmDomain(), Statistic.RECOVERY_TOKENS_PASSED );
+            StatisticsClient.incrementStat( pwmRequest.getPwmDomain(), Statistic.RECOVERY_TOKENS_PASSED );
 
             if ( pwmRequest.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) )
             {
@@ -624,13 +626,13 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
                 if ( otpPassed )
                 {
-                    StatisticsManager.incrementStat( pwmRequest, Statistic.RECOVERY_OTP_PASSED );
+                    StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_OTP_PASSED );
                     LOGGER.debug( pwmRequest, () -> "one time password validation has been passed" );
                     forgottenPasswordBean.getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.OTP );
                 }
                 else
                 {
-                    StatisticsManager.incrementStat( pwmRequest, Statistic.RECOVERY_OTP_FAILED );
+                    StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_OTP_FAILED );
                     handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, new ErrorInformation( PwmError.ERROR_INCORRECT_OTP_TOKEN ) );
                 }
             }
@@ -845,7 +847,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             {
                 final List<FormConfiguration> formConfigurations = pwmRequest.getDomainConfig().readSettingAsForm( PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM );
                 final Map<FormConfiguration, String> formMap = FormUtility.asFormConfigurationMap( formConfigurations, forgottenPasswordBean.getUserSearchValues() );
-                pwmRequest.getPwmDomain().getIntruderManager().convenience().markAttributes( formMap, pwmRequest.getLabel() );
+                pwmRequest.getPwmDomain().getIntruderService().client().markAttributes( formMap, pwmRequest.getLabel() );
             }
 
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE,
@@ -869,7 +871,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         try
         {
             // check attributes
-            final ChaiUser theUser = pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity );
+            final ChaiUser theUser = pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity );
             final Locale userLocale = pwmRequest.getLocale();
 
             final List<FormConfiguration> requiredAttributesForm = forgottenPasswordBean.getAttributeForm();
@@ -933,13 +935,13 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         if ( !forgottenPasswordBean.isAgreementPassed() )
         {
             forgottenPasswordBean.setAgreementPassed( true );
-            final AuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createUserAuditRecord(
+            final AuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                     AuditEvent.AGREEMENT_PASSED,
                     pwmRequest.getUserInfoIfLoggedIn(),
                     pwmRequest.getLabel(),
                     "ForgottenPassword"
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         return ProcessStatus.Continue;
@@ -1095,7 +1097,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         if ( !forgottenPasswordBean.getProgress().isAllPassed() )
         {
             forgottenPasswordBean.getProgress().setAllPassed( true );
-            StatisticsManager.incrementStat( pwmRequest, Statistic.RECOVERY_SUCCESSES );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_SUCCESSES );
         }
 
         final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
@@ -1163,18 +1165,17 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             throws IOException, ServletException, ChaiUnavailableException, PwmUnrecoverableException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
 
         try
         {
-            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userIdentity );
+            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequest.getLabel(), userIdentity );
             theUser.unlockPassword();
 
             // mark the event log
             final UserInfo userInfoBean = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
-            pwmDomain.getAuditManager().submit( AuditEvent.UNLOCK_PASSWORD, userInfoBean, pwmSession );
+            AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.UNLOCK_PASSWORD, userInfoBean );
 
             ForgottenPasswordUtil.sendUnlockNoticeEmail( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
 
@@ -1207,7 +1208,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         }
 
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
-        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userIdentity );
+        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequest.getLabel(), userIdentity );
 
         try
         {
@@ -1219,7 +1220,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         {
             final String errorMsg = "unable to unlock user " + theUser.getEntryDN() + " error: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg );
-            LOGGER.error( pwmRequest, () -> errorInformation.toDebugStr() );
+            LOGGER.error( pwmRequest, errorInformation::toDebugStr );
         }
 
         try
@@ -1235,8 +1236,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             LOGGER.info( pwmRequest, () -> "user successfully supplied password recovery responses, forward to change password page: " + theUser.getEntryDN() );
 
             // mark the event log
-            pwmDomain.getAuditManager().submit( AuditEvent.RECOVER_PASSWORD, pwmSession.getUserInfo(),
-                    pwmSession );
+            AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.RECOVER_PASSWORD, pwmSession.getUserInfo() );
 
             // mark user as requiring a new password.
             pwmSession.getLoginInfoBean().getLoginFlags().add( LoginInfoBean.LoginFlag.forcePwChange );
@@ -1278,12 +1278,12 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                     PwmAuthenticationSource.FORGOTTEN_PASSWORD
             );
             sessionAuthenticator.simulateBadPassword( userIdentity );
-            pwmRequest.getPwmDomain().getIntruderManager().convenience().markUserIdentity( userIdentity, pwmRequest );
+            pwmRequest.getPwmDomain().getIntruderService().client().markUserIdentity( userIdentity, pwmRequest );
         }
 
-        pwmRequest.getPwmDomain().getIntruderManager().convenience().markAddressAndSession( pwmRequest );
+        pwmRequest.getPwmDomain().getIntruderService().client().markAddressAndSession( pwmRequest );
 
-        StatisticsManager.incrementStat( pwmRequest, Statistic.RECOVERY_FAILURES );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_FAILURES );
     }
 
     private void checkForLocaleSwitch( final PwmRequest pwmRequest, final ForgottenPasswordBean forgottenPasswordBean )

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

@@ -38,6 +38,7 @@ import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.bean.ForgottenPasswordStage;
 import password.pwm.ldap.UserInfo;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -323,7 +324,7 @@ class ForgottenPasswordStageProcessor
             if ( !forgottenPasswordBean.getProgress().isAllPassed() )
             {
                 forgottenPasswordBean.getProgress().setAllPassed( true );
-                pwmDomain.getStatisticsManager().incrementValue( Statistic.RECOVERY_SUCCESSES );
+                StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_SUCCESSES );
             }
 
             final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );

+ 21 - 19
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java

@@ -61,7 +61,7 @@ import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
@@ -75,7 +75,7 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
-import password.pwm.util.operations.otp.OTPUserRecord;
+import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.util.password.PasswordUtility;
 import password.pwm.ws.server.PresentableForm;
 import password.pwm.ws.server.PresentableFormRow;
@@ -234,6 +234,7 @@ public class ForgottenPasswordStateMachine
                 throws PwmUnrecoverableException
         {
             final PwmRequestContext pwmRequestContext = forgottenPasswordStateMachine.getRequestContext();
+            final SessionLabel sessionLabel = pwmRequestContext.getSessionLabel();
             final PasswordData password1 = PasswordData.forStringValue( formValues.get( PARAM_PASSWORD ) );
             final PasswordData password2 = PasswordData.forStringValue( formValues.get( PARAM_PASSWORD_CONFIRM ) );
 
@@ -253,9 +254,8 @@ public class ForgottenPasswordStateMachine
                 if ( verifyOnly )
                 {
                     final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
-                            pwmRequestContext.getPwmDomain(),
-                            pwmRequestContext.getLocale(),
-                            pwmRequestContext.getPwmDomain().getProxiedChaiUser( userInfo.getUserIdentity() ),
+                            pwmRequestContext,
+                            pwmRequestContext.getPwmDomain().getProxiedChaiUser( sessionLabel, userInfo.getUserIdentity() ),
                             userInfo,
                             null,
                             password1,
@@ -273,7 +273,7 @@ public class ForgottenPasswordStateMachine
                     PasswordUtility.setPassword(
                             forgottenPasswordStateMachine.getRequestContext().getPwmDomain(),
                             forgottenPasswordStateMachine.getRequestContext().getSessionLabel(),
-                            forgottenPasswordStateMachine.getRequestContext().getPwmDomain().getProxyChaiProvider( userInfo.getUserIdentity().getLdapProfileID() ),
+                            forgottenPasswordStateMachine.getRequestContext().getPwmDomain().getProxyChaiProvider( sessionLabel, userInfo.getUserIdentity().getLdapProfileID() ),
                             userInfo,
                             null,
                             password1 );
@@ -498,7 +498,7 @@ public class ForgottenPasswordStateMachine
 
                 if ( otpPassed )
                 {
-                    pwmRequestContext.getPwmDomain().getStatisticsManager().incrementValue( Statistic.RECOVERY_OTP_PASSED );
+                    StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_OTP_PASSED );
                     forgottenPasswordStateMachine.getForgottenPasswordBean().getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.OTP );
                 }
                 else
@@ -506,7 +506,8 @@ public class ForgottenPasswordStateMachine
                     errorInformation = errorInformation == null
                             ? new ErrorInformation( PwmError.ERROR_INCORRECT_OTP_TOKEN )
                             : errorInformation;
-                    pwmRequestContext.getPwmDomain().getStatisticsManager().incrementValue( Statistic.RECOVERY_OTP_FAILED );
+
+                    StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_OTP_FAILED );
                     handleUserVerificationBadAttempt( pwmRequestContext, forgottenPasswordStateMachine.getForgottenPasswordBean(), errorInformation );
                     throw new PwmUnrecoverableException( errorInformation );
                 }
@@ -587,7 +588,7 @@ public class ForgottenPasswordStateMachine
                     }
 
                     forgottenPasswordStateMachine.getForgottenPasswordBean().getProgress().getSatisfiedMethods().add( IdentityVerificationMethod.TOKEN );
-                    StatisticsManager.incrementStat( pwmRequestContext.getPwmDomain(), Statistic.RECOVERY_TOKENS_PASSED );
+                    StatisticsClient.incrementStat( pwmRequestContext.getPwmDomain(), Statistic.RECOVERY_TOKENS_PASSED );
 
                     if ( pwmRequestContext.getDomainConfig().readSettingAsBoolean( PwmSetting.DISPLAY_TOKEN_SUCCESS_BUTTON ) )
                     {
@@ -744,7 +745,7 @@ public class ForgottenPasswordStateMachine
                     {
                         final List<FormConfiguration> formConfigurations = pwmDomain.getConfig().readSettingAsForm( PwmSetting.FORGOTTEN_PASSWORD_SEARCH_FORM );
                         final Map<FormConfiguration, String> formMap = FormUtility.asFormConfigurationMap( formConfigurations, forgottenPasswordBean.getUserSearchValues() );
-                        pwmDomain.getIntruderManager().convenience().markAttributes( formMap, forgottenPasswordStateMachine.getRequestContext().getSessionLabel() );
+                        pwmDomain.getIntruderService().client().markAttributes( formMap, forgottenPasswordStateMachine.getRequestContext().getSessionLabel() );
                     }
 
                     final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE,
@@ -766,7 +767,7 @@ public class ForgottenPasswordStateMachine
                 try
                 {
                     // check attributes
-                    final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userIdentity );
+                    final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity );
 
                     final List<FormConfiguration> requiredAttributesForm = forgottenPasswordBean.getAttributeForm();
 
@@ -969,7 +970,7 @@ public class ForgottenPasswordStateMachine
                 formValues = FormUtility.readFormValuesFromMap( values, forgottenPasswordForm, pwmRequestContext.getLocale() );
 
                 // check for intruder search values
-                pwmRequestContext.getPwmDomain().getIntruderManager().convenience().checkAttributes( formValues );
+                pwmRequestContext.getPwmDomain().getIntruderService().client().checkAttributes( formValues );
 
                 // see if the values meet the configured form requirements.
                 FormUtility.validateFormValues( pwmRequestContext.getDomainConfig(), formValues, pwmRequestContext.getLocale() );
@@ -1007,12 +1008,12 @@ public class ForgottenPasswordStateMachine
                     throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_CANT_MATCH_USER ) );
                 }
 
-                AuthenticationUtility.checkIfUserEligibleToAuthentication( pwmRequestContext.getPwmDomain(), userIdentity );
+                AuthenticationUtility.checkIfUserEligibleToAuthentication( pwmRequestContext.getSessionLabel(), pwmRequestContext.getPwmDomain(), userIdentity );
 
                 ForgottenPasswordUtil.initForgottenPasswordBean( pwmRequestContext, userIdentity, forgottenPasswordStateMachine.getForgottenPasswordBean() );
 
                 // clear intruder search values
-                pwmRequestContext.getPwmDomain().getIntruderManager().convenience().clearAttributes( formValues );
+                pwmRequestContext.getPwmDomain().getIntruderService().client().clearAttributes( formValues );
 
                 return;
             }
@@ -1024,9 +1025,10 @@ public class ForgottenPasswordStateMachine
                             PwmError.ERROR_RESPONSES_NORESPONSES,
                             e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues()
                     );
-                    pwmRequestContext.getPwmDomain().getStatisticsManager().incrementValue( Statistic.RECOVERY_FAILURES );
 
-                    pwmRequestContext.getPwmDomain().getIntruderManager().convenience().markAttributes( formValues, pwmRequestContext.getSessionLabel() );
+                    StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_FAILURES );
+
+                    pwmRequestContext.getPwmDomain().getIntruderService().client().markAttributes( formValues, pwmRequestContext.getSessionLabel() );
 
                     LOGGER.debug( pwmRequestContext.getSessionLabel(), errorInfo );
                     forgottenPasswordStateMachine.clear();
@@ -1075,7 +1077,7 @@ public class ForgottenPasswordStateMachine
             final LdapProfile selectedProfile = pwmRequestContext.getDomainConfig().getLdapProfiles().getOrDefault(
                     profile,
                     pwmRequestContext.getDomainConfig().getDefaultLdapProfile() );
-            final Map<String, String> selectableContexts = selectedProfile.getSelectableContexts( pwmRequestContext.getPwmDomain() );
+            final Map<String, String> selectableContexts = selectedProfile.getSelectableContexts( pwmRequestContext.getSessionLabel(), pwmRequestContext.getPwmDomain() );
             if ( selectableContexts != null && selectableContexts.size() > 1 )
             {
                 final Map<String, String> labelLocaleMap = LocaleHelper.localeMapToStringMap(
@@ -1117,10 +1119,10 @@ public class ForgottenPasswordStateMachine
             SessionAuthenticator.simulateBadPassword( pwmRequestContext, userIdentity );
 
 
-            pwmRequestContext.getPwmDomain().getIntruderManager().convenience().markUserIdentity( userIdentity,
+            pwmRequestContext.getPwmDomain().getIntruderService().client().markUserIdentity( userIdentity,
                     pwmRequestContext.getSessionLabel() );
         }
 
-        StatisticsManager.incrementStat( pwmRequestContext.getPwmDomain(), Statistic.RECOVERY_FAILURES );
+        StatisticsClient.incrementStat( pwmRequestContext.getPwmDomain(), Statistic.RECOVERY_FAILURES );
     }
 }

+ 10 - 7
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -63,7 +63,9 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
@@ -170,7 +172,7 @@ public class ForgottenPasswordUtil
 
         try
         {
-            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userIdentity );
+            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequestContext.getSessionLabel(), userIdentity );
             responseSet = pwmDomain.getCrService().readUserResponseSet(
                     pwmRequestContext.getSessionLabel(),
                     userIdentity,
@@ -414,7 +416,7 @@ public class ForgottenPasswordUtil
                         .build()
         );
 
-        pwmRequestContext.getPwmDomain().getStatisticsManager().incrementValue( Statistic.RECOVERY_TOKENS_SENT );
+        StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_TOKENS_SENT );
     }
 
 
@@ -434,7 +436,7 @@ public class ForgottenPasswordUtil
         }
 
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
-        final ChaiUser theUser = pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity );
+        final ChaiUser theUser = pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity );
 
         try
         {
@@ -493,12 +495,13 @@ public class ForgottenPasswordUtil
 
             // mark the event log
             {
-                final AuditRecord auditRecord = new AuditRecordFactory( pwmDomain ).createUserAuditRecord(
+                final AuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
                         AuditEvent.RECOVER_PASSWORD,
                         userIdentity,
                         pwmRequest.getLabel()
                 );
-                pwmDomain.getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+                AuditServiceClient.submit( pwmRequest, auditRecord );
             }
 
             final MessageSendMethod messageSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_SENDNEWPW_METHOD, MessageSendMethod.class );
@@ -698,7 +701,7 @@ public class ForgottenPasswordUtil
             final Optional<ResponseSet> responseSet;
             try
             {
-                final ChaiUser theUser = pwmDomain.getProxiedChaiUser( userInfo.getUserIdentity() );
+                final ChaiUser theUser = pwmDomain.getProxiedChaiUser( pwmRequestContext.getSessionLabel(), userInfo.getUserIdentity() );
                 responseSet = pwmDomain.getCrService().readUserResponseSet(
                         sessionLabel,
                         userInfo.getUserIdentity(),
@@ -727,7 +730,7 @@ public class ForgottenPasswordUtil
         {
             try
             {
-                final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( userInfo.getUserIdentity() );
+                final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( pwmRequestContext.getSessionLabel(), userInfo.getUserIdentity() );
                 if ( chaiUser.isPasswordLocked() )
                 {
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTRUDER_LDAP ) );

+ 61 - 48
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -28,8 +28,8 @@ import com.novell.ldapchai.exception.ChaiPasswordPolicyException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.TokenDestinationItem;
 import password.pwm.bean.UserIdentity;
@@ -67,12 +67,17 @@ import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchResults;
+import password.pwm.svc.cr.CrService;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.HelpdeskAuditRecord;
-import password.pwm.svc.intruder.IntruderService;
+import password.pwm.svc.intruder.IntruderDomainService;
+import password.pwm.svc.otp.OTPUserRecord;
+import password.pwm.svc.otp.OtpService;
+import password.pwm.svc.secure.DomainSecureService;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
@@ -84,12 +89,8 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.operations.ActionExecutor;
-import password.pwm.util.operations.CrService;
-import password.pwm.util.operations.OtpService;
 import password.pwm.util.password.PasswordUtility;
-import password.pwm.util.operations.otp.OTPUserRecord;
 import password.pwm.util.password.RandomPasswordGenerator;
-import password.pwm.svc.secure.DomainSecureService;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestCheckPasswordServer;
 import password.pwm.ws.server.rest.RestRandomPasswordServer;
@@ -249,7 +250,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity targetUserIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity targetUserIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
         LOGGER.debug( pwmRequest, () -> "received executeAction request for user " + targetUserIdentity.toString() );
 
         final List<ActionConfiguration> actionConfigurations = helpdeskProfile.readSettingAsAction( PwmSetting.HELPDESK_ACTIONS );
@@ -291,7 +292,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
 
             // mark the event log
             {
-                final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+                final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                         AuditEvent.HELPDESK_ACTION,
                         pwmSession.getUserInfo().getUserIdentity(),
                         action.getName(),
@@ -299,7 +300,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                         pwmSession.getSessionStateBean().getSrcAddress(),
                         pwmSession.getSessionStateBean().getSrcHostname()
                 );
-                pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+                AuditServiceClient.submit( pwmRequest, auditRecord );
             }
             final RestResultBean restResultBean = RestResultBean.forSuccessMessage(
                     pwmRequest.getLocale(),
@@ -339,7 +341,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
         LOGGER.info( pwmRequest, () -> "received deleteUser request by " + pwmSession.getUserInfo().getUserIdentity().toString() + " for user " + userIdentity.toString() );
 
         // check if user should be seen by actor
@@ -363,7 +365,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
 
         // execute user delete operation
         final ChaiProvider provider = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY )
-                ? pwmDomain.getProxyChaiProvider( userIdentity.getLdapProfileID() )
+                ? pwmDomain.getProxyChaiProvider( pwmRequest.getLabel(), userIdentity.getLdapProfileID() )
                 : pwmSession.getSessionManager().getChaiProvider();
 
 
@@ -389,7 +391,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     userIdentity.getUserDN(),
                     userIdentity.getLdapProfileID()
             );
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_DELETE_USER,
                     pwmSession.getUserInfo().getUserIdentity(),
                     null,
@@ -397,7 +399,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmSession.getSessionStateBean().getSrcAddress(),
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
-            pwmDomain.getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         LOGGER.info( pwmRequest, () -> "user " + userIdentity + " has been deleted" );
@@ -595,7 +598,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
 
         if ( !helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_UNLOCK ) )
         {
@@ -607,8 +610,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
 
         //clear pwm intruder setting.
         {
-            final IntruderService intruderManager = pwmRequest.getPwmDomain().getIntruderManager();
-            intruderManager.convenience().clearUserIdentity( userIdentity );
+            final IntruderDomainService intruderManager = pwmRequest.getPwmDomain().getIntruderService();
+            intruderManager.client().clearUserIdentity( userIdentity );
         }
 
 
@@ -622,7 +625,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             chaiUser.unlockPassword();
             {
                 // mark the event log
-                final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+                final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                         AuditEvent.HELPDESK_UNLOCK_PASSWORD,
                         pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
                         null,
@@ -630,7 +633,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                         pwmRequest.getLabel().getSourceAddress(),
                         pwmRequest.getLabel().getSourceHostname()
                 );
-                pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+
+                AuditServiceClient.submit( pwmRequest, auditRecord );
             }
         }
         catch ( final ChaiPasswordPolicyException e )
@@ -675,7 +679,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             pwmRequest.respondWithError( errorInformation, false );
             return ProcessStatus.Halt;
         }
-        final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
 
         if ( !helpdeskProfile.readOptionalVerificationMethods().contains( IdentityVerificationMethod.OTP ) )
         {
@@ -705,7 +709,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             if ( passed )
             {
                 final PwmSession pwmSession = pwmRequest.getPwmSession();
-                final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+                final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                         AuditEvent.HELPDESK_VERIFY_OTP,
                         pwmSession.getUserInfo().getUserIdentity(),
                         null,
@@ -713,15 +717,15 @@ public class HelpdeskServlet extends ControlledPwmServlet
                         pwmSession.getSessionStateBean().getSrcAddress(),
                         pwmSession.getSessionStateBean().getSrcHostname()
                 );
-                pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+                AuditServiceClient.submit( pwmRequest, auditRecord );
 
-                StatisticsManager.incrementStat( pwmRequest, Statistic.HELPDESK_VERIFY_OTP );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_VERIFY_OTP );
                 verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.OTP );
             }
             else
             {
                 final PwmSession pwmSession = pwmRequest.getPwmSession();
-                final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+                final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                         AuditEvent.HELPDESK_VERIFY_OTP_INCORRECT,
                         pwmSession.getUserInfo().getUserIdentity(),
                         null,
@@ -729,7 +733,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                         pwmSession.getSessionStateBean().getSrcAddress(),
                         pwmSession.getSessionStateBean().getSrcHostname()
                 );
-                pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+                AuditServiceClient.submit( pwmRequest, auditRecord );
             }
 
             return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean );
@@ -754,7 +758,10 @@ public class HelpdeskServlet extends ControlledPwmServlet
         final DomainConfig config = pwmRequest.getDomainConfig();
         final Map<String, String> bodyParams = pwmRequest.readBodyAsJsonStringMap();
 
-        final UserIdentity targetUserIdentity = UserIdentity.fromKey( bodyParams.get( PwmConstants.PARAM_USERKEY ), pwmRequest.getPwmApplication() );
+        final UserIdentity targetUserIdentity = UserIdentity.fromKey(
+                pwmRequest.getLabel(),
+                bodyParams.get( PwmConstants.PARAM_USERKEY ),
+                pwmRequest.getPwmApplication() );
         final UserInfo targetUserInfo = HelpdeskServletUtil.getTargetUserInfo( pwmRequest, helpdeskProfile, targetUserIdentity );
 
         final String requestedTokenID = bodyParams.get( "id" );
@@ -819,7 +826,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-        StatisticsManager.incrementStat( pwmRequest, Statistic.HELPDESK_TOKENS_SENT );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_TOKENS_SENT );
         final HelpdeskVerificationRequestBean helpdeskVerificationRequestBean = new HelpdeskVerificationRequestBean();
         helpdeskVerificationRequestBean.setDestination( tokenDestinationItem.getDisplay() );
         helpdeskVerificationRequestBean.setUserKey( bodyParams.get( PwmConstants.PARAM_USERKEY ) );
@@ -862,7 +869,10 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 HelpdeskVerificationRequestBean.TokenData.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( helpdeskVerificationRequestBean.getUserKey(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey(
+                pwmRequest.getLabel(),
+                helpdeskVerificationRequestBean.getUserKey(),
+                pwmRequest.getPwmApplication() );
 
         if ( tokenData == null || tokenData.getIssueDate() == null || tokenData.getToken() == null || tokenData.getToken().isEmpty() )
         {
@@ -891,7 +901,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         if ( passed )
         {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_VERIFY_TOKEN,
                     pwmSession.getUserInfo().getUserIdentity(),
                     null,
@@ -899,13 +909,13 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmSession.getSessionStateBean().getSrcAddress(),
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
             verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.TOKEN );
         }
         else
         {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_VERIFY_TOKEN_INCORRECT,
                     pwmSession.getUserInfo().getUserIdentity(),
                     null,
@@ -913,7 +923,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmSession.getSessionStateBean().getSrcAddress(),
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean );
@@ -941,7 +951,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         }
 
         //clear pwm intruder setting.
-        pwmRequest.getPwmDomain().getIntruderManager().convenience().clearUserIdentity( userIdentity );
+        pwmRequest.getPwmDomain().getIntruderService().client().clearUserIdentity( userIdentity );
 
         try
         {
@@ -951,7 +961,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             service.clearOTPUserConfiguration( pwmRequest, userIdentity, chaiUser );
             {
                 // mark the event log
-                final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+                final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                         AuditEvent.HELPDESK_CLEAR_OTP_SECRET,
                         pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
                         null,
@@ -959,7 +969,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                         pwmRequest.getLabel().getSourceAddress(),
                         pwmRequest.getLabel().getSourceHostname()
                 );
-                pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+                AuditServiceClient.submit( pwmRequest, auditRecord );
             }
         }
         catch ( final PwmOperationalException e )
@@ -1036,7 +1046,10 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 HelpdeskVerificationRequestBean.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( helpdeskVerificationRequestBean.getUserKey(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey(
+                pwmRequest.getLabel(),
+                helpdeskVerificationRequestBean.getUserKey(),
+                pwmRequest.getPwmApplication() );
 
         boolean passed = false;
         {
@@ -1084,7 +1097,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         if ( passed )
         {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_VERIFY_ATTRIBUTES,
                     pwmSession.getUserInfo().getUserIdentity(),
                     null,
@@ -1092,13 +1105,13 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmSession.getSessionStateBean().getSrcAddress(),
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
             verificationStateBean.addRecord( userIdentity, IdentityVerificationMethod.ATTRIBUTES );
         }
         else
         {
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_VERIFY_ATTRIBUTES_INCORRECT,
                     pwmSession.getUserInfo().getUserIdentity(),
                     null,
@@ -1106,7 +1119,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmSession.getSessionStateBean().getSrcAddress(),
                     pwmSession.getSessionStateBean().getSrcHostname()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         return outputVerificationResponseBean( pwmRequest, passed, verificationStateBean );
@@ -1180,7 +1193,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
 
         // mark the event log
         {
-            final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest.getPwmDomain(), pwmRequest ).createHelpdeskAuditRecord(
+            final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                     AuditEvent.HELPDESK_CLEAR_RESPONSES,
                     pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
                     null,
@@ -1188,7 +1201,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                     pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress(),
                     pwmRequest.getPwmSession().getSessionStateBean().getSrcHostname()
             );
-            pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+            AuditServiceClient.submit( pwmRequest, auditRecord );
         }
 
         final RestResultBean restResultBean = RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown );
@@ -1204,7 +1217,8 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 RestCheckPasswordServer.JsonInput.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( jsonInput.getUsername(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey(
+                pwmRequest.getLabel(), jsonInput.getUsername(), pwmRequest.getPwmApplication() );
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
 
         HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity );
@@ -1229,8 +1243,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
         }
 
         final PasswordUtility.PasswordCheckInfo passwordCheckInfo = PasswordUtility.checkEnteredPassword(
-                pwmRequest.getPwmDomain(),
-                pwmRequest.getLocale(),
+                pwmRequest.getPwmRequestContext(),
                 chaiUser,
                 userInfo,
                 null,
@@ -1256,7 +1269,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 RestSetPasswordServer.JsonInputData.class
         );
 
-        final UserIdentity userIdentity = UserIdentity.fromKey( jsonInput.getUsername(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), jsonInput.getUsername(), pwmRequest.getPwmApplication() );
         final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity );
         final UserInfo userInfo = UserInfoFactory.newUserInfo(
                 pwmRequest.getPwmApplication(),
@@ -1335,7 +1348,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
     private ProcessStatus processRandomPasswordAction( final PwmRequest pwmRequest ) throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final RestRandomPasswordServer.JsonInput input = JsonUtil.deserialize( pwmRequest.readRequestBodyAsString(), RestRandomPasswordServer.JsonInput.class );
-        final UserIdentity userIdentity = UserIdentity.fromKey( input.getUsername(), pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), input.getUsername(), pwmRequest.getPwmApplication() );
 
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
 
@@ -1391,7 +1404,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing" );
             throw new PwmUnrecoverableException( errorInformation );
         }
-        return UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        return UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
     }
 
     static PhotoDataReader photoDataReader( final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity )

+ 10 - 9
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java

@@ -22,8 +22,8 @@ package password.pwm.http.servlet.helpdesk;
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
@@ -37,15 +37,16 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.permission.UserPermissionType;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.HelpdeskAuditRecord;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -210,9 +211,9 @@ public class HelpdeskServletUtil
     )
             throws ChaiUnavailableException, PwmUnrecoverableException
     {
-        final UserIdentity actorUserIdentity = pwmRequest.getUserInfoIfLoggedIn().canonicalized( pwmRequest.getPwmApplication() );
+        final UserIdentity actorUserIdentity = pwmRequest.getUserInfoIfLoggedIn().canonicalized( pwmRequest.getLabel(), pwmRequest.getPwmApplication() );
 
-        if ( actorUserIdentity.canonicalEquals( userIdentity, pwmRequest.getPwmApplication() ) )
+        if ( actorUserIdentity.canonicalEquals( pwmRequest.getLabel(), userIdentity, pwmRequest.getPwmApplication() ) )
         {
             final String errorMsg = "cannot select self";
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, errorMsg );
@@ -233,7 +234,7 @@ public class HelpdeskServletUtil
         }
 
         final HelpdeskDetailInfoBean helpdeskDetailInfoBean = HelpdeskDetailInfoBean.makeHelpdeskDetailInfo( pwmRequest, helpdeskProfile, userIdentity );
-        final HelpdeskAuditRecord auditRecord = new AuditRecordFactory( pwmRequest ).createHelpdeskAuditRecord(
+        final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(
                 AuditEvent.HELPDESK_VIEW_DETAIL,
                 pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
                 null,
@@ -241,9 +242,9 @@ public class HelpdeskServletUtil
                 pwmRequest.getLabel().getSourceAddress(),
                 pwmRequest.getLabel().getSourceHostname()
         );
-        pwmRequest.getPwmDomain().getAuditManager().submit( pwmRequest.getLabel(), auditRecord );
+        AuditServiceClient.submit( pwmRequest, auditRecord );
 
-        StatisticsManager.incrementStat( pwmRequest, Statistic.HELPDESK_USER_LOOKUP );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.HELPDESK_USER_LOOKUP );
         return helpdeskDetailInfoBean;
     }
 
@@ -329,7 +330,7 @@ public class HelpdeskServletUtil
     {
         final boolean useProxy = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_USE_PROXY );
         return useProxy
-                ? pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity )
+                ? pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity )
                 : pwmRequest.getPwmSession().getSessionManager().getActor( userIdentity );
     }
 

+ 8 - 3
server/src/main/java/password/pwm/http/servlet/newuser/NewUserFormUtils.java

@@ -30,12 +30,12 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.NewUserBean;
+import password.pwm.svc.secure.DomainSecureService;
 import password.pwm.svc.token.TokenPayload;
 import password.pwm.util.PasswordData;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.svc.secure.DomainSecureService;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -43,6 +43,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.NoSuchElementException;
 
 class NewUserFormUtils
 {
@@ -62,8 +63,12 @@ class NewUserFormUtils
         final List<FormConfiguration> newUserForm = NewUserServlet.getFormDefinition( pwmRequest );
         final Map<FormConfiguration, String> userFormValues = FormUtility.readFormValuesFromRequest( pwmRequest,
                 newUserForm, userLocale );
-        final PasswordData passwordData1 = pwmRequest.readParameterAsPassword( NewUserServlet.FIELD_PASSWORD1 );
-        final PasswordData passwordData2 = pwmRequest.readParameterAsPassword( NewUserServlet.FIELD_PASSWORD2 );
+        final PasswordData passwordData1 = pwmRequest.readParameterAsPassword( NewUserServlet.FIELD_PASSWORD1 )
+                .orElseThrow( () -> new NoSuchElementException( "missing " +  NewUserServlet.FIELD_PASSWORD1 + " field" ) );
+
+        final PasswordData passwordData2 = pwmRequest.readParameterAsPassword( NewUserServlet.FIELD_PASSWORD2 )
+                .orElseThrow( () -> new NoSuchElementException( "missing " +  NewUserServlet.FIELD_PASSWORD2 + " field" ) );
+
 
         final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
         return injectRemoteValuesIntoForm( userFormValues, newUserBean.getRemoteInputData(), newUserProfile, passwordData1, passwordData2 );

+ 6 - 6
server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java

@@ -238,7 +238,7 @@ public class NewUserServlet extends ControlledPwmServlet
         // try to read the new user policy to make sure it's readable, that way an exception is thrown here instead of by the jsp
         {
             final Instant startTime = Instant.now();
-            newUserProfile.getNewUserPasswordPolicy( pwmDomain, pwmSession.getSessionStateBean().getLocale() );
+            newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() );
             LOGGER.trace( () -> "read new user password policy in ", () -> TimeDuration.fromCurrent( startTime ) );
         }
 
@@ -340,7 +340,7 @@ public class NewUserServlet extends ControlledPwmServlet
     }
 
     private boolean readProfileFromUrl( final PwmRequest pwmRequest, final NewUserBean newUserBean )
-            throws PwmUnrecoverableException, ServletException, IOException
+            throws PwmUnrecoverableException, IOException
     {
         final String profileUrlSegment = "profile";
         final String urlRemainder = servletUriRemainder( pwmRequest, profileUrlSegment );
@@ -434,6 +434,7 @@ public class NewUserServlet extends ControlledPwmServlet
             validationFlags.add( FormUtility.ValidationFlag.allowResultCaching );
         }
         FormUtility.validateFormValueUniqueness(
+                pwmRequest.getLabel(),
                 pwmDomain,
                 formValueData,
                 locale,
@@ -445,7 +446,7 @@ public class NewUserServlet extends ControlledPwmServlet
 
         final UserInfo uiBean = UserInfoBean.builder()
                 .cachedPasswordRuleAttributes( FormUtility.asStringMap( formValueData ) )
-                .passwordPolicy( newUserProfile.getNewUserPasswordPolicy( pwmDomain, locale ) )
+                .passwordPolicy( newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() ) )
                 .build();
 
         final boolean promptForPassword = newUserProfile.readSettingAsBoolean( PwmSetting.NEWUSER_PROMPT_FOR_PASSWORD );
@@ -455,8 +456,7 @@ public class NewUserServlet extends ControlledPwmServlet
         if ( promptForPassword )
         {
             passwordCheckInfo =  PasswordUtility.checkEnteredPassword(
-                    pwmDomain,
-                    locale,
+                    pwmRequest.getPwmRequestContext(),
                     null,
                     uiBean,
                     null,
@@ -698,7 +698,7 @@ public class NewUserServlet extends ControlledPwmServlet
         {
             final TimeDuration elapsedTime = TimeDuration.fromCurrent( startTime );
             complete = false;
-            percentComplete = new Percent( elapsedTime.asMillis(), minWaitTime ).asBigDecimal();
+            percentComplete = Percent.of( elapsedTime.asMillis(), minWaitTime ).asBigDecimal();
         }
 
         final LinkedHashMap<String, Object> outputMap = new LinkedHashMap<>();

+ 9 - 8
server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java

@@ -62,7 +62,9 @@ import password.pwm.ldap.auth.SessionAuthenticator;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.svc.event.AuditEvent;
+import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
@@ -131,7 +133,6 @@ class NewUserUtils
             throws PwmUnrecoverableException, ChaiUnavailableException, PwmOperationalException
     {
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
 
         final long startTime = System.currentTimeMillis();
 
@@ -157,7 +158,7 @@ class NewUserUtils
         }
         else
         {
-            final PwmPasswordPolicy pwmPasswordPolicy = newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmDomain(), pwmRequest.getLocale() );
+            final PwmPasswordPolicy pwmPasswordPolicy = newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() );
             userPassword = RandomPasswordGenerator.createRandomPassword( pwmRequest.getLabel(), pwmPasswordPolicy, pwmRequest.getPwmDomain() );
         }
 
@@ -174,7 +175,7 @@ class NewUserUtils
             createObjectClasses.addAll( defaultLDAPProfile.readSettingAsStringArray( PwmSetting.AUTO_ADD_OBJECT_CLASSES ) );
         }
 
-        final ChaiProvider chaiProvider = newUserProfile.getLdapProfile( pwmDomain.getConfig() ).getProxyChaiProvider( pwmDomain );
+        final ChaiProvider chaiProvider = newUserProfile.getLdapProfile( pwmDomain.getConfig() ).getProxyChaiProvider( pwmRequest.getLabel(), pwmDomain );
         try
         {
             // create the ldap entry
@@ -211,7 +212,7 @@ class NewUserUtils
             final PasswordData temporaryPassword;
             {
                 final RandomPasswordGenerator.RandomGeneratorConfig randomGeneratorConfig = RandomPasswordGenerator.RandomGeneratorConfig.builder()
-                        .passwordPolicy( newUserProfile.getNewUserPasswordPolicy( pwmDomain, pwmRequest.getLocale() ) )
+                        .passwordPolicy( newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() ) )
                         .build();
                 temporaryPassword = RandomPasswordGenerator.createRandomPassword( pwmRequest.getLabel(), randomGeneratorConfig, pwmDomain );
             }
@@ -324,7 +325,7 @@ class NewUserUtils
 
                 final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmDomain, userIdentity )
                         .setExpandPwmMacros( true )
-                        .setMacroMachine( pwmSession.getSessionManager().getMacroMachine( ) )
+                        .setMacroMachine( pwmRequest.getPwmSession().getSessionManager().getMacroMachine( ) )
                         .createActionExecutor();
 
                 actionExecutor.executeActions( actions, pwmRequest.getLabel() );
@@ -336,10 +337,10 @@ class NewUserUtils
 
 
         // add audit record
-        pwmDomain.getAuditManager().submit( AuditEvent.CREATE_USER, pwmSession.getUserInfo(), pwmSession );
+        AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.CREATE_USER, pwmRequest.getPwmSession().getUserInfo() );
 
         // increment the new user creation statistics
-        pwmDomain.getStatisticsManager().incrementValue( Statistic.NEW_USERS );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.NEW_USERS );
 
         NewUserUtils.LOGGER.debug( pwmRequest, () -> "completed createUser process for " + newUserDN + " (" + TimeDuration.fromCurrent(
                 startTime ).asCompactString() + ")" );
@@ -355,7 +356,7 @@ class NewUserUtils
         {
             final NewUserProfile newUserProfile = NewUserServlet.getNewUserProfile( pwmRequest );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "deleting ldap user account " + userDN );
-            newUserProfile.getLdapProfile( pwmRequest.getPwmDomain().getConfig() ).getProxyChaiProvider( pwmRequest.getPwmDomain() ).deleteEntry( userDN );
+            newUserProfile.getLdapProfile( pwmRequest.getPwmDomain().getConfig() ).getProxyChaiProvider( pwmRequest.getLabel(), pwmRequest.getPwmDomain() ).deleteEntry( userDN );
             NewUserUtils.LOGGER.warn( pwmRequest, () -> "ldap user account " + userDN + " has been deleted" );
         }
         catch ( final ChaiUnavailableException | ChaiOperationException e )

+ 4 - 1
server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java

@@ -255,7 +255,10 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
                         null,
                         pwmRequest.getLabel()
                 );
-                if ( resolvedIdentity != null && resolvedIdentity.canonicalEquals( pwmSession.getUserInfo().getUserIdentity(), pwmDomain.getPwmApplication() ) )
+                if ( resolvedIdentity != null && resolvedIdentity.canonicalEquals(
+                        pwmRequest.getLabel(),
+                        pwmSession.getUserInfo().getUserIdentity(),
+                        pwmDomain.getPwmApplication() ) )
                 {
                     LOGGER.debug( pwmRequest, () -> "verified incoming oauth code for already authenticated session does resolve to same as logged in user" );
                 }

+ 11 - 11
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -61,7 +61,7 @@ import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CacheLoader;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
@@ -123,12 +123,12 @@ class PeopleSearchDataReader
             if ( cachedResult != null )
             {
                 final SearchResultBean copyWithCacheSet = cachedResult.toBuilder().fromCache( true ).build();
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
                 return copyWithCacheSet;
             }
             else
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
             }
         }
 
@@ -136,7 +136,7 @@ class PeopleSearchDataReader
         final SearchResultBean searchResultBean = makeSearchResultsImpl( searchRequestBean )
                 .toBuilder().fromCache( false ).build();
 
-        StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_SEARCHES );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_SEARCHES );
         storeDataInCache( cacheKey, searchResultBean );
         LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.getSearchResults().size()
                 + " results for search request "
@@ -163,13 +163,13 @@ class PeopleSearchDataReader
             final OrgChartDataBean cachedOutput = pwmRequest.getPwmDomain().getCacheService().get( cacheKey, OrgChartDataBean.class );
             if ( cachedOutput != null )
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
                 LOGGER.trace( pwmRequest, () -> "completed makeOrgChartData of " + userIdentity.toDisplayString() + " from cache" );
                 return cachedOutput;
             }
             else
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
             }
         }
 
@@ -250,12 +250,12 @@ class PeopleSearchDataReader
             final UserDetailBean cachedOutput = pwmRequest.getPwmDomain().getCacheService().get( cacheKey, UserDetailBean.class );
             if ( cachedOutput != null )
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS );
                 return cachedOutput;
             }
             else
             {
-                StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
+                StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES );
             }
         }
 
@@ -590,7 +590,7 @@ class PeopleSearchDataReader
             throws PwmUnrecoverableException
     {
         final Locale locale = pwmRequest.getLocale();
-        final ChaiProvider chaiProvider = pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity ).getChaiProvider();
+        final ChaiProvider chaiProvider = pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity ).getChaiProvider();
         final UserInfo userInfo = UserInfoFactory.newUserInfo(
                 pwmRequest.getPwmApplication(),
                 pwmRequest.getLabel(),
@@ -699,7 +699,7 @@ class PeopleSearchDataReader
     {
         final boolean useProxy = useProxy();
         return useProxy
-                ? pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity )
+                ? pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity )
                 : pwmRequest.getPwmSession().getSessionManager().getActor( userIdentity );
     }
 
@@ -842,7 +842,7 @@ class PeopleSearchDataReader
                 final String userKey = ( String ) map.get( "userKey" );
                 if ( userKey != null )
                 {
-                    final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+                    final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
                     final String displayValue = figureDisplaynameValue( pwmRequest, userIdentity );
                     map.put( "_displayName", displayValue );
                 }

+ 7 - 14
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchService.java

@@ -21,10 +21,10 @@
 package password.pwm.http.servlet.peoplesearch;
 
 import password.pwm.PwmApplication;
-import password.pwm.PwmDomain;
 import password.pwm.bean.DomainID;
 import password.pwm.error.PwmException;
 import password.pwm.health.HealthRecord;
+import password.pwm.svc.AbstractPwmService;
 import password.pwm.svc.PwmService;
 import password.pwm.util.PwmScheduler;
 
@@ -35,26 +35,17 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
-public class PeopleSearchService implements PwmService
+public class PeopleSearchService extends AbstractPwmService implements PwmService
 {
-    private PwmDomain pwmDomain;
     private ThreadPoolExecutor threadPoolExecutor;
 
     @Override
-    public STATUS status()
-    {
-        return STATUS.OPEN;
-    }
-
-    @Override
-    public void init( final PwmApplication pwmApplication, final DomainID domainID )
+    public STATUS postAbstractInit( final PwmApplication pwmApplication, final DomainID domainID )
             throws PwmException
     {
-        this.pwmDomain = pwmApplication.domains().get( domainID );
-
         final int maxThreadCount = 5;
 
-        final ThreadFactory threadFactory = PwmScheduler.makePwmThreadFactory( PwmScheduler.makeThreadName( pwmDomain.getPwmApplication(), PeopleSearchService.class ), true );
+        final ThreadFactory threadFactory = PwmScheduler.makePwmThreadFactory( PwmScheduler.makeThreadName( pwmApplication, PeopleSearchService.class ), true );
         threadPoolExecutor = new ThreadPoolExecutor(
                 maxThreadCount,
                 maxThreadCount,
@@ -63,6 +54,8 @@ public class PeopleSearchService implements PwmService
                 new ArrayBlockingQueue<>( 5000 ),
                 threadFactory
         );
+
+        return STATUS.OPEN;
     }
 
     @Override
@@ -72,7 +65,7 @@ public class PeopleSearchService implements PwmService
     }
 
     @Override
-    public List<HealthRecord> healthCheck()
+    public List<HealthRecord> serviceHealthCheck()
     {
         return Collections.emptyList();
     }

+ 4 - 4
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -43,7 +43,7 @@ import password.pwm.http.servlet.peoplesearch.bean.SearchResultBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserDetailBean;
 import password.pwm.ldap.PhotoDataBean;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -194,7 +194,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
 
             addExpiresHeadersToResponse( pwmRequest );
             pwmRequest.outputJsonResult( RestResultBean.withData( orgChartData ) );
-            StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_ORGCHART );
+            StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_ORGCHART );
         }
         catch ( final PwmException e )
         {
@@ -220,7 +220,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
 
         addExpiresHeadersToResponse( pwmRequest );
         pwmRequest.outputJsonResult( RestResultBean.withData( detailData ) );
-        pwmRequest.getPwmDomain().getStatisticsManager().incrementValue( Statistic.PEOPLESEARCH_DETAILS );
+        StatisticsClient.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_DETAILS );
 
         return ProcessStatus.Halt;
     }
@@ -347,7 +347,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
 
         final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
         final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
-        final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
+        final UserIdentity userIdentity = UserIdentity.fromKey( pwmRequest.getLabel(), userKey, pwmRequest.getPwmApplication() );
         peopleSearchDataReader.checkIfUserIdentityViewable( userIdentity );
         return userIdentity;
     }

+ 1 - 1
server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java

@@ -217,7 +217,7 @@ public class PhotoDataReader
     {
         return LdapOperationsHelper.readPhotoDataFromLdap(
                 pwmRequest.getDomainConfig(),
-                pwmRequest.getPwmDomain().getProxiedChaiUser( userIdentity ).getChaiProvider(),
+                pwmRequest.getPwmDomain().getProxiedChaiUser( pwmRequest.getLabel(), userIdentity ).getChaiProvider(),
                 userIdentity
         );
     }

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java

@@ -32,7 +32,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.servlet.PwmServlet;
 import password.pwm.svc.stats.Statistic;
-import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.MovingAverage;
 import password.pwm.util.java.TimeDuration;
@@ -214,7 +214,7 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
             pwmRequest.debugHttpRequestToLog( debugText, () -> TimeDuration.fromCurrent( pwmRequest.getRequestStartTime() ) );
 
             final MovingAverage cacheHitRatio = resourceService.getCacheHitRatio();
-            StatisticsManager.incrementStat( pwmDomain, Statistic.HTTP_RESOURCE_REQUESTS );
+            StatisticsClient.incrementStat( pwmDomain, Statistic.HTTP_RESOURCE_REQUESTS );
             cacheHitRatio.update( fromCache ? 1 : 0 );
         }
         catch ( final Exception e )

+ 22 - 16
server/src/main/java/password/pwm/http/servlet/resource/ResourceServletConfiguration.java

@@ -26,6 +26,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import password.pwm.AppProperty;
 import password.pwm.PwmDomain;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.FileValue;
@@ -81,9 +82,9 @@ class ResourceServletConfiguration
         nonceValue = null;
     }
 
-    private ResourceServletConfiguration( final PwmDomain pwmDomain )
+    private ResourceServletConfiguration( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
-        LOGGER.trace( () -> "initializing" );
+        LOGGER.trace( sessionLabel, () -> "initializing" );
         final DomainConfig domainConfig = pwmDomain.getConfig();
         maxCacheItems = Integer.parseInt( domainConfig.readAppProperty( AppProperty.HTTP_RESOURCES_MAX_CACHE_ITEMS ) );
         cacheExpireSeconds = Long.parseLong( domainConfig.readAppProperty( AppProperty.HTTP_RESOURCES_EXPIRATION_SECONDS ) );
@@ -95,12 +96,14 @@ class ResourceServletConfiguration
         noncePattern = Pattern.compile( noncePrefix + "[^/]*?/" );
         nonceValue = pwmDomain.getPwmApplication().getRuntimeNonce();
 
-        zipResources = makeZipResourcesFromConfig( pwmDomain, domainConfig );
+        zipResources = makeZipResourcesFromConfig( sessionLabel, pwmDomain, domainConfig );
 
-        customFileBundle = makeCustomFileBundle( domainConfig );
+        customFileBundle = makeCustomFileBundle( sessionLabel, domainConfig );
     }
 
-    private Map<String, FileResource> makeCustomFileBundle( final DomainConfig domainConfig )
+    private Map<String, FileResource> makeCustomFileBundle(
+            final SessionLabel sessionLabel,
+            final DomainConfig domainConfig )
     {
         final Map<String, FileResource> customFileBundle = new HashMap<>();
         final Map<FileValue.FileInformation, FileValue.FileContent> files = domainConfig.readSettingAsFile( PwmSetting.DISPLAY_CUSTOM_RESOURCE_BUNDLE );
@@ -109,7 +112,7 @@ class ResourceServletConfiguration
             final Map.Entry<FileValue.FileInformation, FileValue.FileContent> entry = files.entrySet().iterator().next();
             final FileValue.FileInformation fileInformation = entry.getKey();
             final FileValue.FileContent fileContent = entry.getValue();
-            LOGGER.debug( () -> "examining configured zip file resource for items name=" + fileInformation.getFilename() + ", size=" + fileContent.size() );
+            LOGGER.debug( sessionLabel, () -> "examining configured zip file resource for items name=" + fileInformation.getFilename() + ", size=" + fileContent.size() );
 
             try
             {
@@ -117,18 +120,21 @@ class ResourceServletConfiguration
                 final String path = "/tmp/" + domainConfig.getDomainID().stringValue();
                 FileUtils.writeByteArrayToFile( new File( path ), bytes );
 
-                final Map<String, FileResource> customFiles = makeMemoryFileMapFromZipInput( fileContent.getContents() );
+                final Map<String, FileResource> customFiles = makeMemoryFileMapFromZipInput( sessionLabel, fileContent.getContents() );
                 customFileBundle.putAll( customFiles );
             }
             catch ( final IOException e )
             {
-                LOGGER.error( () -> "error assembling memory file map zip bundle: " + e.getMessage() );
+                LOGGER.error( sessionLabel, () -> "error assembling memory file map zip bundle: " + e.getMessage() );
             }
         }
         return Collections.unmodifiableMap( customFileBundle );
     }
 
-    private Map<String, ZipFile> makeZipResourcesFromConfig( final PwmDomain pwmDomain, final DomainConfig domainConfig )
+    private Map<String, ZipFile> makeZipResourcesFromConfig(
+            final SessionLabel sessionLabel,
+            final PwmDomain pwmDomain,
+            final DomainConfig domainConfig )
     {
         final Map<String, ZipFile> zipResources = new HashMap<>();
         final String zipFileResourceParam = domainConfig.getAppConfig().readAppProperty( AppProperty.HTTP_RESOURCES_ZIP_FILES );
@@ -151,25 +157,25 @@ class ResourceServletConfiguration
                         );
                         final ZipFile zipFile = new ZipFile( zipFileFile );
                         zipResources.put( ResourceFileServlet.RESOURCE_PATH + configuredZipFileResource.getUrl(), zipFile );
-                        LOGGER.debug( () -> "registered resource-zip file " + configuredZipFileResource.getZipFile() + " at path " + zipFileFile.getAbsolutePath() );
+                        LOGGER.debug( sessionLabel, () -> "registered resource-zip file " + configuredZipFileResource.getZipFile() + " at path " + zipFileFile.getAbsolutePath() );
                     }
                     catch ( final IOException e )
                     {
-                        LOGGER.warn( () -> "unable to resource-zip file " + configuredZipFileResource + ", error: " + e.getMessage() );
+                        LOGGER.warn( sessionLabel, () -> "unable to resource-zip file " + configuredZipFileResource + ", error: " + e.getMessage() );
                     }
                 }
                 else
                 {
-                    LOGGER.error( () -> "can't register resource-zip file " + configuredZipFileResource.getZipFile() + " because WEB-INF path is unknown" );
+                    LOGGER.error( sessionLabel, () -> "can't register resource-zip file " + configuredZipFileResource.getZipFile() + " because WEB-INF path is unknown" );
                 }
             }
         }
         return Collections.unmodifiableMap( zipResources );
     }
 
-    static ResourceServletConfiguration fromConfig( final PwmDomain pwmDomain )
+    static ResourceServletConfiguration fromConfig( final SessionLabel sessionLabel, final PwmDomain pwmDomain )
     {
-        return new ResourceServletConfiguration( pwmDomain );
+        return new ResourceServletConfiguration( sessionLabel, pwmDomain );
     }
 
     static ResourceServletConfiguration defaultConfiguration( )
@@ -177,7 +183,7 @@ class ResourceServletConfiguration
         return new ResourceServletConfiguration();
     }
 
-    private static Map<String, FileResource> makeMemoryFileMapFromZipInput( final ImmutableByteArray content )
+    private static Map<String, FileResource> makeMemoryFileMapFromZipInput( final SessionLabel sessionLabel, final ImmutableByteArray content )
             throws IOException
     {
         final ZipInputStream stream = new ZipInputStream( content.newByteArrayInputStream() );
@@ -196,7 +202,7 @@ class ResourceServletConfiguration
                 memoryMap.put( name, new MemoryFileResource( name, contents, lastModified ) );
                 {
                     final String finalEntry = entry.getName();
-                    LOGGER.trace( () -> "discovered file in configured resource bundle: " + finalEntry );
+                    LOGGER.trace( sessionLabel, () -> "discovered file in configured resource bundle: " + finalEntry );
                 }
             }
         }

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác