فهرست منبع

Merge branch 'master' into patch-14

Jason Rivard 5 سال پیش
والد
کامیت
711f9f1603
100فایلهای تغییر یافته به همراه2399 افزوده شده و 1729 حذف شده
  1. 19 0
      .gitignore
  2. 7 8
      build/checkstyle.xml
  3. 4 0
      build/license-header-jsp.txt
  4. 11 10
      client/package.json
  5. 2 2
      client/pom.xml
  6. 1 1
      client/src/modules/helpdesk/main.ts
  7. 1 1
      client/src/modules/peoplesearch/main.ts
  8. 3 3
      data-service/pom.xml
  9. 4 0
      data-service/src/main/webapp/WEB-INF/jsp/telemetry-viewer.jsp
  10. 4 0
      data-service/src/main/webapp/index.jsp
  11. 1 1
      docker/pom.xml
  12. 1 1
      onejar/pom.xml
  13. 4 0
      onejar/src/main/resources/ROOT-redirect-webapp/WEB-INF/index.jsp
  14. 9 17
      pom.xml
  15. 1 1
      pwm-cr/pom.xml
  16. 2 2
      rest-test-service/src/main/java/password/pwm/resttest/RestTestPasswordCheckServlet.java
  17. 4 0
      rest-test-service/src/main/webapp/index.jsp
  18. 26 10
      server/pom.xml
  19. 6 0
      server/src/main/java/password/pwm/AppProperty.java
  20. 1 1
      server/src/main/java/password/pwm/PwmAboutProperty.java
  21. 16 6
      server/src/main/java/password/pwm/PwmApplication.java
  22. 1 0
      server/src/main/java/password/pwm/PwmConstants.java
  23. 2 2
      server/src/main/java/password/pwm/bean/LocalSessionStateBean.java
  24. 61 107
      server/src/main/java/password/pwm/config/Configuration.java
  25. 48 41
      server/src/main/java/password/pwm/config/PwmSetting.java
  26. 5 0
      server/src/main/java/password/pwm/config/PwmSettingCategory.java
  27. 3 3
      server/src/main/java/password/pwm/config/PwmSettingXml.java
  28. 59 0
      server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java
  29. 1 1
      server/src/main/java/password/pwm/config/profile/ChallengeProfile.java
  30. 11 9
      server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java
  31. 11 8
      server/src/main/java/password/pwm/config/profile/EmailServerProfile.java
  32. 11 11
      server/src/main/java/password/pwm/config/profile/ForgottenPasswordProfile.java
  33. 11 9
      server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java
  34. 19 9
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  35. 12 10
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  36. 60 0
      server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java
  37. 8 3
      server/src/main/java/password/pwm/config/profile/Profile.java
  38. 159 0
      server/src/main/java/password/pwm/config/profile/ProfileDefinition.java
  39. 0 62
      server/src/main/java/password/pwm/config/profile/ProfileType.java
  40. 34 2
      server/src/main/java/password/pwm/config/profile/ProfileUtility.java
  41. 1 1
      server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  42. 3 3
      server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java
  43. 10 8
      server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java
  44. 11 9
      server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java
  45. 4 1
      server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java
  46. 0 23
      server/src/main/java/password/pwm/error/ErrorInformation.java
  47. 13 9
      server/src/main/java/password/pwm/health/ApplianceStatusChecker.java
  48. 3 1
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  49. 3 2
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  50. 38 29
      server/src/main/java/password/pwm/http/HttpContentType.java
  51. 27 0
      server/src/main/java/password/pwm/http/HttpEntityDataType.java
  52. 34 15
      server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java
  53. 1 1
      server/src/main/java/password/pwm/http/JspUrl.java
  54. 1 1
      server/src/main/java/password/pwm/http/PwmHttpResponseWrapper.java
  55. 1 1
      server/src/main/java/password/pwm/http/PwmResponse.java
  56. 3 1
      server/src/main/java/password/pwm/http/PwmURL.java
  57. 15 9
      server/src/main/java/password/pwm/http/SessionManager.java
  58. 3 0
      server/src/main/java/password/pwm/http/bean/ActivateUserBean.java
  59. 20 1
      server/src/main/java/password/pwm/http/bean/ImmutableByteArray.java
  60. 0 28
      server/src/main/java/password/pwm/http/bean/UserSessionDataCacheBean.java
  61. 10 296
      server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  62. 125 99
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  63. 10 15
      server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  64. 2 2
      server/src/main/java/password/pwm/http/servlet/DeleteAccountServlet.java
  65. 2 0
      server/src/main/java/password/pwm/http/servlet/PwmServletDefinition.java
  66. 28 26
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  67. 36 66
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java
  68. 3 1
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  69. 2 2
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java
  70. 16 12
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  71. 0 1
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  72. 1 1
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServletUtil.java
  73. 5 2
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  74. 2 0
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  75. 3 3
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java
  76. 420 0
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java
  77. 18 13
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  78. 6 6
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java
  79. 0 83
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  80. 6 11
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  81. 14 13
      server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java
  82. 3 58
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java
  83. 20 24
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  84. 14 8
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java
  85. 78 59
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java
  86. 65 110
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  87. 0 56
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchResourcesServlet.java
  88. 53 54
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java
  89. 312 0
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  90. 2 9
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PrivatePeopleSearchServlet.java
  91. 2 12
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PublicPeopleSearchServlet.java
  92. 2 8
      server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/PeopleSearchClientConfigBean.java
  93. 3 1
      server/src/main/java/password/pwm/http/servlet/resource/ResourceFileRequest.java
  94. 2 2
      server/src/main/java/password/pwm/http/servlet/resource/ResourceFileServlet.java
  95. 3 3
      server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java
  96. 237 77
      server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java
  97. 23 15
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java
  98. 1 1
      server/src/main/java/password/pwm/ldap/LdapBrowser.java
  99. 16 30
      server/src/main/java/password/pwm/ldap/LdapConnectionService.java
  100. 20 66
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java

+ 19 - 0
.gitignore

@@ -26,3 +26,22 @@
 /webapp/src/main/webapp/public/resources/themes/idm
 /webapp/src/main/webapp/public/resources/themes/mdefault
 /webapp/src/main/webapp/public/resources/themes/mdefault44
+
+# VS Code Project Files
+.vscode/*
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+**/.settings/
+*/.settings/
+**/*.classpath
+*/*.classpath
+**/*.factorypath
+*/*.factorypath
+**/*.project
+*/*.project
+
+

+ 7 - 8
build/checkstyle.xml

@@ -20,8 +20,8 @@
   -->
 
 <!DOCTYPE module PUBLIC
-        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
-        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+        "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
+        "https://checkstyle.org/dtds/configuration_1_3.dtd">
 
 <!--
   PWM Checkstyle definition
@@ -52,7 +52,11 @@
         <property name="headerFile" value="${checkstyle.header.file}"/>
     </module>
     -->
-
+    <module name="LineLength">
+        <property name="max" value="180" />
+        <property name="ignorePattern" value="@version|@see|@todo|TODO"/>
+        <property name="fileExtensions" value="java"/>
+    </module>
     <module name="FileTabCharacter">
         <property name="eachLine" value="true"/>
     </module>
@@ -78,11 +82,6 @@
             <property name="allowNonPrintableEscapes" value="true"/>
         </module>
 
-        <module name="LineLength">
-            <property name="max" value="180" />
-            <property name="ignorePattern" value="@version|@see|@todo|TODO"/>
-        </module>
-
         <module name="EmptyBlock">
             <property name="option" value="TEXT"/>
             <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>

+ 4 - 0
build/license-header-jsp.txt

@@ -17,3 +17,7 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
 --%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>

+ 11 - 10
client/package.json

@@ -17,14 +17,15 @@
     "author": "",
     "license": "ISC",
     "dependencies": {
-        "@microfocus/ias-icons": "1.0.1",
-        "@microfocus/ng-ias": "1.0.0-alpha.2",
-        "@microfocus/ux-ias": "1.0.0-rc",
+        "@microfocus/ias-icons": "1.0.4",
+        "@microfocus/ng-ias": "1.0.1",
+        "@microfocus/ux-ias": "1.1.2",
         "@uirouter/angularjs": "1.0.15",
-        "angular": "1.6.9",
-        "angular-aria": "1.6.9",
-        "angular-sanitize": "1.6.9",
-        "angular-translate": "2.17.0",
+        "angular": "1.7.8",
+        "angular-aria": "1.7.8",
+        "angular-sanitize": "1.7.8",
+        "angular-translate": "2.18.1",
+        "core-js": "3.2.1",
         "textangular": "1.5.16"
     },
     "devDependencies": {
@@ -37,7 +38,7 @@
         "angular-mocks": "1.6.9",
         "autoprefixer": "8.1.0",
         "copy-webpack-plugin": "4.5.1",
-        "css-loader": "0.28.10",
+        "css-loader": "^3.2.0",
         "file-loader": "1.1.11",
         "html-loader": "0.5.5",
         "html-webpack-plugin": "3.0.6",
@@ -48,7 +49,7 @@
         "jshint": "^2.10.2",
         "jshint-loader": "0.8.4",
         "json-loader": "0.5.7",
-        "karma": "3.1.1",
+        "karma": "^4.3.0",
         "karma-chrome-launcher": "2.2.0",
         "karma-jasmine": "1.1.2",
         "karma-jasmine-html-reporter": "1.3.1",
@@ -75,7 +76,7 @@
         "uglifyjs-webpack-plugin": "1.2.3",
         "url-loader": "1.0.1",
         "webpack": "4.1.1",
-        "webpack-cli": "2.0.12",
+        "webpack-cli": "^3.3.8",
         "webpack-dev-server": "3.1.14",
         "webpack-merge": "4.1.2",
         "write-file-webpack-plugin": "4.2.0"

+ 2 - 2
client/pom.xml

@@ -79,9 +79,9 @@
             <plugin>
                 <groupId>com.github.eirslett</groupId>
                 <artifactId>frontend-maven-plugin</artifactId>
-                <version>1.7.6</version>
+                <version>1.8.0</version>
                 <configuration>
-                    <nodeVersion>v10.16.0</nodeVersion>
+                    <nodeVersion>v10.16.3</nodeVersion>
                     <npmVersion>6.9.0</npmVersion>
                     <installDirectory>.node</installDirectory>
                 </configuration>

+ 1 - 1
client/src/modules/helpdesk/main.ts

@@ -24,7 +24,7 @@ import 'angular-sanitize';
 import '@microfocus/ng-ias/dist/ng-ias';
 
 // Add a polyfill for Set() for IE11, since it's used in peoplesearch-base.component.ts
-import 'core-js/es6/set';
+import 'core-js/es/set';
 
 import { bootstrap, module } from 'angular';
 import helpDeskModule from './helpdesk.module';

+ 1 - 1
client/src/modules/peoplesearch/main.ts

@@ -23,7 +23,7 @@ import 'angular-translate';
 import '@microfocus/ng-ias/dist/ng-ias';
 
 // Add a polyfill for Set() for IE11, since it's used in peoplesearch-base.component.ts
-import 'core-js/es6/set';
+import 'core-js/es/set';
 
 import { bootstrap, module } from 'angular';
 import ConfigService from '../../services/peoplesearch-config.service';

+ 3 - 3
data-service/pom.xml

@@ -137,12 +137,12 @@
         <dependency>
             <groupId>com.sun.mail</groupId>
             <artifactId>jakarta.mail</artifactId>
-            <version>1.6.3</version>
+            <version>1.6.4</version>
         </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.5.9</version>
+            <version>4.5.10</version>
         </dependency>
         <dependency>
             <groupId>log4j</groupId>
@@ -162,7 +162,7 @@
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
             <artifactId>xodus-environment</artifactId>
-            <version>1.3.0</version>
+            <version>1.3.91</version>
         </dependency>
     </dependencies>
 </project>

+ 4 - 0
data-service/src/main/webapp/WEB-INF/jsp/telemetry-viewer.jsp

@@ -17,6 +17,10 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
 --%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>
 <%@ page import="password.pwm.receiver.SummaryBean" %>
 <%@ page import="password.pwm.receiver.TelemetryViewerServlet" %>
 <%@ page import="java.time.Instant" %>

+ 4 - 0
data-service/src/main/webapp/index.jsp

@@ -17,6 +17,10 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
 --%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>
 
 
 <!DOCTYPE html>

+ 1 - 1
docker/pom.xml

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

+ 1 - 1
onejar/pom.xml

@@ -17,7 +17,7 @@
 
     <properties>
         <project.root.basedir>${project.basedir}/..</project.root.basedir>
-        <tomcat.version>9.0.21</tomcat.version>
+        <tomcat.version>9.0.24</tomcat.version>
     </properties>
 
     <build>

+ 4 - 0
onejar/src/main/resources/ROOT-redirect-webapp/WEB-INF/index.jsp

@@ -17,6 +17,10 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
 --%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>
 
 
 <%@ page session="false" contentType="text/html" %>

+ 9 - 17
pom.xml

@@ -37,7 +37,6 @@
         <project.root.basedir>${project.basedir}</project.root.basedir>
 
         <skipTests>false</skipTests>
-        <skipSpotbugs>false</skipSpotbugs>
     </properties>
 
     <modules>
@@ -58,12 +57,6 @@
                 <checkstyle.skip>true</checkstyle.skip>
             </properties>
         </profile>
-        <profile>
-            <id>skip-spotbugs</id>
-            <properties>
-                <skipSpotbugs>true</skipSpotbugs>
-            </properties>
-        </profile>
         <profile>
             <id>skip-javadoc</id>
             <properties>
@@ -77,7 +70,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.1.0</version>
+                <version>3.1.1</version>
                 <executions>
                     <execution>
                         <goals>
@@ -172,7 +165,7 @@
                     <dependency>
                         <groupId>com.puppycrawl.tools</groupId>
                         <artifactId>checkstyle</artifactId>
-                        <version>8.22</version>
+                        <version>8.24</version>
                     </dependency>
                 </dependencies>
                 <executions>
@@ -251,7 +244,7 @@
             <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
-                <version>3.1.12</version>
+                <version>3.1.12.2</version>
                 <dependencies>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
@@ -263,12 +256,11 @@
                     <fork>false</fork>
                     <excludeFilterFile>${project.root.basedir}/build/spotbugs-exclude.xml</excludeFilterFile>
                     <includeTests>false</includeTests>
-                    <skip>${skipSpotbugs}</skip>
                     <effort>max</effort>
                 </configuration>
                 <executions>
                     <execution>
-                        <phase>test</phase>
+                        <phase>verify</phase>
                         <goals>
                             <goal>check</goal>
                         </goals>
@@ -278,7 +270,7 @@
             <plugin> <!-- checks owsp vulnerability database -->
                 <groupId>org.owasp</groupId>
                 <artifactId>dependency-check-maven</artifactId>
-                <version>5.0.0</version>
+                <version>5.2.1</version>
                 <executions>
                     <execution>
                         <goals>
@@ -295,7 +287,7 @@
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <version>1.18.8</version>
+            <version>1.18.10</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -315,19 +307,19 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>2.28.2</version>
+            <version>3.0.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
-            <version>3.12.2</version>
+            <version>3.13.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.github.tomakehurst</groupId>
             <artifactId>wiremock</artifactId>
-            <version>2.23.2</version>
+            <version>2.24.1</version>
             <scope>test</scope>
         </dependency>
         <dependency>

+ 1 - 1
pwm-cr/pom.xml

@@ -45,7 +45,7 @@
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcpkix-jdk15on</artifactId>
-            <version>1.62</version>
+            <version>1.63</version>
         </dependency>
 
         <!-- Test dependencies -->

+ 2 - 2
rest-test-service/src/main/java/password/pwm/resttest/RestTestPasswordCheckServlet.java

@@ -49,8 +49,8 @@ public class RestTestPasswordCheckServlet extends HttpServlet
                 }.getType() );
         final String inputPassword = inputJson.get( "password" );
         final boolean error = inputPassword.contains( "aaa" );
-
-        final String errorMessage = error ? "TOO Many aaa's (REMOTE REST SERVICE)" : "No error. (REMOTE REST SERVICE)";
+        final String errorMessage = ( error ? "TOO Many aaa's (REMOTE REST SERVICE)" : "No error. (REMOTE REST SERVICE)" )
+                + ", pw=" + inputPassword;
 
         resp.setHeader( "Content-Type", "application/json" );
         final PrintWriter writer = resp.getWriter();

+ 4 - 0
rest-test-service/src/main/webapp/index.jsp

@@ -17,6 +17,10 @@
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
 --%>
+<%--
+       THIS FILE IS NOT INTENDED FOR END USER MODIFICATION.
+       See the README.TXT file in WEB-INF/jsp before making changes.
+--%>
 
 
 <!DOCTYPE html>

+ 26 - 10
server/pom.xml

@@ -202,7 +202,7 @@
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-text</artifactId>
-            <version>1.6</version>
+            <version>1.8</version>
         </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
@@ -227,12 +227,12 @@
         <dependency>
             <groupId>com.sun.mail</groupId>
             <artifactId>jakarta.mail</artifactId>
-            <version>1.6.3</version>
+            <version>1.6.4</version>
         </dependency>
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.5.9</version>
+            <version>4.5.10</version>
         </dependency>
         <dependency>
             <groupId>org.graylog2</groupId>
@@ -257,12 +257,12 @@
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
-            <version>1.62</version>
+            <version>1.63</version>
         </dependency>
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcpkix-jdk15on</artifactId>
-            <version>1.62</version>
+            <version>1.63</version>
         </dependency>
         <dependency>
             <groupId>jaxen</groupId>
@@ -292,13 +292,22 @@
         <dependency>
             <groupId>com.blueconic</groupId>
             <artifactId>browscap-java</artifactId>
-            <version>1.2.9</version>
+            <version>1.2.11</version>
         </dependency>
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
             <artifactId>xodus-environment</artifactId>
-            <version>1.3.0</version>
+            <!-- do not upgrade to 1.3.x until bug is resolved: https://youtrack.jetbrains.com/issue/XD-786 -->
+            <version>1.2.3</version>
         </dependency>
+
+        <dependency>
+            <!-- added because of older xodus depedendency -->
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib</artifactId>
+            <version>1.3.50</version>
+        </dependency>
+
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-nop</artifactId>
@@ -307,17 +316,17 @@
         <dependency>
             <groupId>org.webjars</groupId>
             <artifactId>webjars-locator-core</artifactId>
-            <version>0.37</version>
+            <version>0.40</version>
         </dependency>
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
-            <version>2.7.0</version>
+            <version>2.8.0</version>
         </dependency>
         <dependency>
             <groupId>com.nulab-inc</groupId>
             <artifactId>zxcvbn</artifactId>
-            <version>1.2.5</version>
+            <version>1.2.7</version>
         </dependency>
         <dependency>
             <groupId>com.github.ziplet</groupId>
@@ -330,6 +339,13 @@
                 </exclusion>
             </exclusions>
         </dependency>
+
+        <dependency>
+            <!-- added newer dependency of xodus-environment -->
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.19</version>
+        </dependency>
     </dependencies>
 
     <repositories>

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

@@ -129,6 +129,7 @@ public enum AppProperty
     HTTP_CLIENT_SOCKET_TIMEOUT_MS                   ( "http.client.socketTimeoutMs" ),
     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_ENABLE_HOSTNAME_VERIFICATION        ( "http.client.enableHostnameVerification" ),
     HTTP_CLIENT_PROMISCUOUS_WORDLIST_ENABLE         ( "http.client.promiscuous.wordlist.enable" ),
     HTTP_ENABLE_GZIP                                ( "http.gzip.enable" ),
@@ -276,6 +277,8 @@ public enum AppProperty
     PASSWORD_STRENGTH_THRESHOLD_WEAK                ( "password.strength.threshold.weak" ),
     PASSWORD_STRENGTH_THRESHOLD_VERY_WEAK           ( "password.strength.threshold.veryWeak" ),
     PASSWORD_RULE_WORDLIST_FAIL_WHEN_CLOSED         ( "password.rule.wordlist.failWhenClosed" ),
+    PHOTO_CLIENT_CACHE_SECONDS                      ( "photo.clientCacheTimeSeconds" ),
+    PHOTO_INTERNAL_HTTP_PROXY_ENABLE                ( "photo.internalHttpProxy.enable" ),
     PWNOTIFY_BATCH_COUNT                            ( "pwNotify.batch.count" ),
     PWNOTIFY_BATCH_DELAY_TIME_MULTIPLIER            ( "pwNotify.batch.delayTimeMultiplier" ),
     PWNOTIFY_MAX_LDAP_SEARCH_SIZE                   ( "pwNotify.maxLdapSearchSize" ),
@@ -362,7 +365,10 @@ public enum AppProperty
     WORDLIST_IMPORT_DURATION_GOAL_MS                ( "wordlist.import.durationGoalMS" ),
     WORDLIST_IMPORT_MIN_TRANSACTIONS                ( "wordlist.import.minTransactions" ),
     WORDLIST_IMPORT_MAX_TRANSACTIONS                ( "wordlist.import.maxTransactions" ),
+    WORDLIST_IMPORT_MAX_CHARS_TRANSACTIONS          ( "wordlist.import.maxCharsTransactions" ),
+    WORDLIST_IMPORT_LINE_COMMENTS                   ( "wordlist.import.lineComments" ),
     WORDLIST_INSPECTOR_FREQUENCY_SECONDS            ( "wordlist.inspector.frequencySeconds" ),
+    WORDLIST_TEST_MODE                              ( "wordlist.testMode" ),
     WS_REST_CLIENT_PWRULE_HALTONERROR               ( "ws.restClient.pwRule.haltOnError" ),
     WS_REST_SERVER_SIGNING_FORM_TIMEOUT_SECONDS     ( "ws.restServer.signing.form.timeoutSeconds" ),
     WS_REST_SERVER_STATISTICS_DEFAULT_HISTORY       ( "ws.restServer.statistics.defaultHistoryDays" ),

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

@@ -52,7 +52,7 @@ public enum PwmAboutProperty
     app_mode_manageHttps( null, pwmApplication -> Boolean.toString( pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.ManageHttps ) ) ),
     app_applicationPath( null, pwmApplication -> pwmApplication.getPwmEnvironment().getApplicationPath().getAbsolutePath() ),
     app_environmentFlags( null, pwmApplication -> StringUtil.collectionToString( pwmApplication.getPwmEnvironment().getFlags() ) ),
-    app_wordlistSize( null, pwmApplication -> Long.toString( pwmApplication.getWordlistManager().size() ) ),
+    app_wordlistSize( null, pwmApplication -> Long.toString( pwmApplication.getWordlistService().size() ) ),
     app_seedlistSize( null, pwmApplication -> Long.toString( pwmApplication.getSeedlistManager().size() ) ),
     app_sharedHistorySize( null, pwmApplication -> Long.toString( pwmApplication.getSharedHistoryManager().size() ) ),
     app_sharedHistoryOldestTime( null, pwmApplication -> format( pwmApplication.getSharedHistoryManager().getOldestEntryTime() ) ),

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

@@ -46,6 +46,7 @@ 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.httpclient.HttpClientService;
 import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.node.NodeService;
@@ -483,11 +484,15 @@ public class PwmApplication
             }
 
             final ByteArrayOutputStream outputContents = new ByteArrayOutputStream();
-            ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
-                    pwmApplication.getConfig(),
-                    new FileInputStream( tomcatSourceFile ),
-                    outputContents
-            );
+            try ( FileInputStream fileInputStream = new FileInputStream( tomcatOutputFile ) )
+            {
+                ExportHttpsTomcatConfigCommand.TomcatConfigWriter.writeOutputFile(
+                        pwmApplication.getConfig(),
+                        fileInputStream,
+                        outputContents
+                );
+            }
+
             if ( tomcatOutputFile.exists() )
             {
                 LOGGER.trace( () -> "deleting existing tomcat configuration file " + tomcatOutputFile.getAbsolutePath() );
@@ -551,6 +556,11 @@ public class PwmApplication
         return ( HealthMonitor ) pwmServiceManager.getService( HealthMonitor.class );
     }
 
+    public HttpClientService getHttpClientService()
+    {
+        return ( HttpClientService ) pwmServiceManager.getService( HttpClientService.class );
+    }
+
     public List<PwmService> getPwmServices( )
     {
         final List<PwmService> pwmServices = new ArrayList<>();
@@ -560,7 +570,7 @@ public class PwmApplication
         return Collections.unmodifiableList( pwmServices );
     }
 
-    public WordlistService getWordlistManager( )
+    public WordlistService getWordlistService( )
     {
         return ( WordlistService ) pwmServiceManager.getService( WordlistService.class );
     }

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

@@ -217,6 +217,7 @@ public abstract class PwmConstants
             "Your password must be scanned by the TSA to ensure the safety of your fellow travelers.  Please take off your password's shoes to continue.",
             "That password really tied the room together dude.",
             "Bite my shiny metal password!",
+            "ben makes password software go woooo",
 
             //nick helm
             "I needed a password eight characters long so I picked Snow White and the Seven Dwarves.",

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

@@ -22,7 +22,7 @@ package password.pwm.bean;
 
 import lombok.Data;
 import password.pwm.ldap.UserInfoBean;
-import password.pwm.util.EventRateMeter;
+import password.pwm.util.java.MovingAverage;
 import password.pwm.util.java.TimeDuration;
 
 import java.io.Serializable;
@@ -68,7 +68,7 @@ public class LocalSessionStateBean implements Serializable
 
     private final AtomicInteger intruderAttempts = new AtomicInteger( 0 );
     private final AtomicInteger requestCount = new AtomicInteger( 0 );
-    private final EventRateMeter.MovingAverage avgRequestDuration = new EventRateMeter.MovingAverage( TimeDuration.DAY );
+    private final MovingAverage avgRequestDuration = new MovingAverage( TimeDuration.DAY );
     private boolean oauthInProgress;
 
     private boolean sessionIdRecycleNeeded;

+ 61 - 107
server/src/main/java/password/pwm/config/Configuration.java

@@ -30,15 +30,16 @@ import password.pwm.config.option.CertificateMatchingMode;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.TokenStorageMethod;
+import password.pwm.config.profile.ActivateUserProfile;
 import password.pwm.config.profile.ChallengeProfile;
-import password.pwm.config.profile.DeleteAccountProfile;
 import password.pwm.config.profile.EmailServerProfile;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
+import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.Profile;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
@@ -69,8 +70,8 @@ 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.util.i18n.LocaleHelper;
 import password.pwm.util.PasswordData;
+import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
@@ -91,6 +92,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.function.Supplier;
@@ -159,24 +161,7 @@ public class Configuration implements SettingReader
 
     public Map<String, LdapProfile> getLdapProfiles( )
     {
-        if ( dataCache.ldapProfiles != null )
-        {
-            return dataCache.ldapProfiles;
-        }
-
-        final List<String> profiles = StoredConfigurationUtil.profilesForSetting( PwmSetting.LDAP_PROFILE_LIST, storedConfiguration );
-        final LinkedHashMap<String, LdapProfile> returnList = new LinkedHashMap<>();
-        for ( final String profileID : profiles )
-        {
-            final LdapProfile ldapProfile = LdapProfile.makeFromStoredConfiguration( this.storedConfiguration, profileID );
-            if ( ldapProfile.readSettingAsBoolean( PwmSetting.LDAP_PROFILE_ENABLED ) )
-            {
-                returnList.put( profileID, ldapProfile );
-            }
-        }
-
-        dataCache.ldapProfiles = Collections.unmodifiableMap( returnList );
-        return dataCache.ldapProfiles;
+        return getProfileMap( ProfileDefinition.LdapProfile, LdapProfile.class );
     }
 
     public EmailItemBean readSettingAsEmail( final PwmSetting setting, final Locale locale )
@@ -1036,10 +1021,9 @@ public class Configuration implements SettingReader
     {
         private final Map<String, Map<Locale, PwmPasswordPolicy>> cachedPasswordPolicy = new LinkedHashMap<>();
         private Map<Locale, String> localeFlagMap = null;
-        private Map<String, LdapProfile> ldapProfiles;
         private final Map<PwmSetting, StoredValue> settings = new EnumMap<>( PwmSetting.class );
         private final Map<String, Map<Locale, String>> customText = new LinkedHashMap<>();
-        private final Map<ProfileType, Map<String, Profile>> profileCache = new LinkedHashMap<>();
+        private final Map<ProfileDefinition, Map> profileCache = new LinkedHashMap<>();
         private Map<String, String> appPropertyOverrides = null;
     }
 
@@ -1059,126 +1043,86 @@ public class Configuration implements SettingReader
     }
 
     /* generic profile stuff */
-
-
     public Map<String, NewUserProfile> getNewUserProfiles( )
     {
-        final Map<String, NewUserProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.NewUser );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( NewUserProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.NewUser, NewUserProfile.class );
+    }
+
+    public Map<String, ActivateUserProfile> getUserActivationProfiles( )
+    {
+        return getProfileMap( ProfileDefinition.ActivateUser, ActivateUserProfile.class );
     }
 
     public Map<String, HelpdeskProfile> getHelpdeskProfiles( )
     {
-        final Map<String, HelpdeskProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.Helpdesk );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( HelpdeskProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.Helpdesk, HelpdeskProfile.class );
     }
 
     public Map<String, EmailServerProfile> getEmailServerProfiles( )
     {
-        final Map<String, EmailServerProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.EmailServers );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( EmailServerProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.EmailServers, EmailServerProfile.class );
+    }
+
+    public Map<String, PeopleSearchProfile> getPeopleSearchProfiles( )
+    {
+        return getProfileMap( ProfileDefinition.PeopleSearch, PeopleSearchProfile.class );
     }
 
     public Map<String, SetupOtpProfile> getSetupOTPProfiles( )
     {
-        final Map<String, SetupOtpProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.SetupOTPProfile );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( SetupOtpProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.SetupOTPProfile, SetupOtpProfile.class );
     }
 
     public Map<String, UpdateProfileProfile> getUpdateAttributesProfile( )
     {
-        final Map<String, UpdateProfileProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.UpdateAttributes );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( UpdateProfileProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.UpdateAttributes, UpdateProfileProfile.class );
     }
 
     public Map<String, ForgottenPasswordProfile> getForgottenPasswordProfiles( )
     {
-        final Map<String, ForgottenPasswordProfile> returnMap = new LinkedHashMap<>();
-        final Map<String, Profile> profileMap = profileMap( ProfileType.ForgottenPassword );
-        for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
-        {
-            returnMap.put( entry.getKey(), ( ForgottenPasswordProfile ) entry.getValue() );
-        }
-        return returnMap;
+        return getProfileMap( ProfileDefinition.ForgottenPassword, ForgottenPasswordProfile.class );
     }
 
-    public Map<String, Profile> profileMap( final ProfileType profileType )
+    private <T extends Profile> Map<String, T> getProfileMap( final ProfileDefinition profileDefinition, final Class<T> classOfT  )
     {
-        if ( !dataCache.profileCache.containsKey( profileType ) )
+        if ( !dataCache.profileCache.containsKey( profileDefinition ) )
         {
-            dataCache.profileCache.put( profileType, new LinkedHashMap<>() );
-            for ( final String profileID : ProfileUtility.profileIDsForCategory( this, profileType.getCategory() ) )
+            final Map<String, T> returnMap = new LinkedHashMap<>();
+            final Map<String, Profile> profileMap = profileMap( profileDefinition );
+            for ( final Map.Entry<String, Profile> entry : profileMap.entrySet() )
             {
-                final Profile newProfile = newProfileForID( profileType, profileID );
-                dataCache.profileCache.get( profileType ).put( profileID, newProfile );
+                returnMap.put( entry.getKey(), ( T ) entry.getValue() );
             }
+            dataCache.profileCache.put( profileDefinition, Collections.unmodifiableMap( returnMap ) );
         }
-        return dataCache.profileCache.get( profileType );
+        return dataCache.profileCache.get( profileDefinition );
     }
 
-    private Profile newProfileForID( final ProfileType profileType, final String profileID )
+    public Map<String, Profile> profileMap( final ProfileDefinition profileDefinition )
     {
-        final Profile newProfile;
-        switch ( profileType )
+        final Map<String, Profile> returnMap = new LinkedHashMap<>();
+        for ( final String profileID : ProfileUtility.profileIDsForCategory( this, profileDefinition.getCategory() ) )
         {
-            case Helpdesk:
-                newProfile = HelpdeskProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
-
-            case ForgottenPassword:
-                newProfile = ForgottenPasswordProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
-
-            case NewUser:
-                newProfile = NewUserProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
-
-            case UpdateAttributes:
-                newProfile = UpdateProfileProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
-
-            case DeleteAccount:
-                newProfile = DeleteAccountProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
-
-            case EmailServers:
-                newProfile = EmailServerProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
+            final Profile newProfile = newProfileForID( profileDefinition, profileID );
+            returnMap.put( profileID, newProfile );
+        }
+        return Collections.unmodifiableMap( returnMap );
+    }
 
-            case SetupOTPProfile:
-                newProfile = SetupOtpProfile.makeFromStoredConfiguration( storedConfiguration, profileID );
-                break;
+    private Profile newProfileForID( final ProfileDefinition profileDefinition, final String profileID )
+    {
+        final Class<? extends Profile.ProfileFactory> profileFactoryClass = profileDefinition.getProfileFactoryClass();
 
-            default:
-                throw new IllegalArgumentException( "unknown profile type: " + profileType.toString() );
+        final Profile.ProfileFactory profileFactory;
+        try
+        {
+            profileFactory = profileFactoryClass.getDeclaredConstructor().newInstance();
         }
-
-        return newProfile;
+        catch ( InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e )
+        {
+            throw new IllegalStateException( "unable to create profile instance for " + profileDefinition );
+        }
+        return profileFactory.makeFromStoredConfiguration( storedConfiguration, profileID );
     }
 
     public StoredConfigurationImpl getStoredConfiguration( ) throws PwmUnrecoverableException
@@ -1222,4 +1166,14 @@ public class Configuration implements SettingReader
 
     }
 
+    public Optional<PeopleSearchProfile> getPublicPeopleSearchProfile()
+    {
+        if ( readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
+        {
+            final String profileID = readSettingAsString( PwmSetting.PEOPLE_SEARCH_PUBLIC_PROFILE );
+            final Map<String, PeopleSearchProfile> profiles = this.getProfileMap( ProfileDefinition.PeopleSearchPublic, PeopleSearchProfile.class );
+            return Optional.ofNullable( profiles.get( profileID ) );
+        }
+        return Optional.empty();
+    }
 }

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

@@ -514,6 +514,10 @@ public enum PwmSetting
             "password.policy.maximumOldPasswordChars", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_MINIMUM_LIFETIME(
             "password.policy.minimumLifetime", PwmSettingSyntax.DURATION, PwmSettingCategory.PASSWORD_POLICY ),
+    PASSWORD_POLICY_MAXIMUM_CONSECUTIVE(
+            "password.policy.maximumConsecutive", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
+    PASSWORD_POLICY_MINIMUM_STRENGTH(
+            "password.policy.minimumStrength", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_ENABLE_WORDLIST(
             "password.policy.checkWordlist", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_AD_COMPLEXITY_LEVEL(
@@ -528,10 +532,6 @@ public enum PwmSetting
             "password.policy.disallowedValues", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_DISALLOWED_ATTRIBUTES(
             "password.policy.disallowedAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PASSWORD_POLICY ),
-    PASSWORD_POLICY_MINIMUM_STRENGTH(
-            "password.policy.minimumStrength", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
-    PASSWORD_POLICY_MAXIMUM_CONSECUTIVE(
-            "password.policy.maximumConsecutive", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_CHANGE_MESSAGE(
             "password.policy.changeMessage", PwmSettingSyntax.LOCALIZED_TEXT_AREA, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_RULE_TEXT(
@@ -763,8 +763,6 @@ public enum PwmSetting
             "recovery.response.writePreference", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_SETTINGS ),
     CHALLENGE_STORAGE_HASHED(
             "response.hashMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_SETTINGS ),
-    FORGOTTEN_USER_POST_ACTIONS(
-            "recovery.postActions", PwmSettingSyntax.ACTION, PwmSettingCategory.RECOVERY_SETTINGS ),
     RECOVERY_BOGUS_USER_ENABLE(
             "recovery.bogus.user.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_SETTINGS ),
 
@@ -793,6 +791,8 @@ public enum PwmSetting
             "recovery.token.resend.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_OPTIONS ),
     RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS(
             "recovery.minimumPasswordLifetimeOptions", PwmSettingSyntax.SELECT, PwmSettingCategory.RECOVERY_OPTIONS ),
+    RECOVERY_POST_ACTIONS(
+            "recovery.postActions", PwmSettingSyntax.ACTION, PwmSettingCategory.RECOVERY_OPTIONS ),
 
 
     // recovery oauth
@@ -892,23 +892,27 @@ public enum PwmSetting
 
     // activation settings
     ACTIVATE_USER_ENABLE(
-            "activateUser.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVATION ),
-    ACTIVATE_USER_UNLOCK(
-            "activateUser.allowUnlock", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVATION ),
-    ACTIVATE_TOKEN_SEND_METHOD(
-            "activateUser.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.ACTIVATION ),
-    ACTIVATE_AGREEMENT_MESSAGE(
-            "display.activateUser.agreement", PwmSettingSyntax.LOCALIZED_TEXT_AREA, PwmSettingCategory.ACTIVATION ),
+            "activateUser.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVATION_SETTINGS ),
     ACTIVATE_USER_FORM(
-            "activateUser.form", PwmSettingSyntax.FORM, PwmSettingCategory.ACTIVATION ),
+            "activateUser.form", PwmSettingSyntax.FORM, PwmSettingCategory.ACTIVATION_SETTINGS ),
     ACTIVATE_USER_SEARCH_FILTER(
-            "activateUser.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.ACTIVATION ),
+            "activateUser.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.ACTIVATION_SETTINGS ),
+
+    ACTIVATE_USER_PROFILE_LIST(
+            "activateUser.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
+
     ACTIVATE_USER_QUERY_MATCH(
-            "activateUser.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ACTIVATION ),
+            "activateUser.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.ACTIVATION_PROFILE ),
+    ACTIVATE_USER_UNLOCK(
+            "activateUser.allowUnlock", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.ACTIVATION_PROFILE ),
+    ACTIVATE_TOKEN_SEND_METHOD(
+            "activateUser.token.sendMethod", PwmSettingSyntax.SELECT, PwmSettingCategory.ACTIVATION_PROFILE ),
+    ACTIVATE_AGREEMENT_MESSAGE(
+            "display.activateUser.agreement", PwmSettingSyntax.LOCALIZED_TEXT_AREA, PwmSettingCategory.ACTIVATION_PROFILE ),
     ACTIVATE_USER_PRE_WRITE_ATTRIBUTES(
-            "activateUser.writePreAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.ACTIVATION ),
+            "activateUser.writePreAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.ACTIVATION_PROFILE ),
     ACTIVATE_USER_POST_WRITE_ATTRIBUTES(
-            "activateUser.writePostAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.ACTIVATION ),
+            "activateUser.writePostAttributes", PwmSettingSyntax.ACTION, PwmSettingCategory.ACTIVATION_PROFILE ),
 
     // update profile
     UPDATE_PROFILE_ENABLE(
@@ -952,45 +956,49 @@ public enum PwmSetting
 
     // peoplesearch settings
     PEOPLE_SEARCH_ENABLE(
-            "peopleSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_SETTINGS ),
+    PEOPLE_SEARCH_ENABLE_PUBLIC(
+            "peopleSearch.enablePublic", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_SETTINGS ),
+    PEOPLE_SEARCH_PUBLIC_PROFILE(
+            "peopleSearch.public.profile", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH_SETTINGS ),
+    PEOPLESEARCH_PROFILE_LIST(
+            "peopleSearch.profile.list", PwmSettingSyntax.PROFILE, PwmSettingCategory.INTERNAL ),
     PEOPLE_SEARCH_QUERY_MATCH(
-            "peopleSearch.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_SEARCH_FORM(
-            "peopleSearch.search.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.search.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_RESULT_FORM(
-            "peopleSearch.result.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.result.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_DETAIL_FORM(
-            "peopleSearch.detail.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.detail.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_RESULT_LIMIT(
-            "peopleSearch.result.limit", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.result.limit", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_USE_PROXY(
-            "peopleSearch.useProxy", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.useProxy", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_DISPLAY_NAMES_CARD_LABELS(
-            "peopleSearch.displayName.cardLabels", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.displayName.cardLabels", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_MAX_CACHE_SECONDS(
-            "peopleSearch.maxCacheSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.maxCacheSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_PHOTO(
-            "peopleSearch.enablePhoto", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enablePhoto", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_PHOTO_QUERY_FILTER(
-            "peopleSearch.photo.queryFilter", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.photo.queryFilter", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_SEARCH_FILTER(
-            "peopleSearch.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_SEARCH_BASE(
-            "peopleSearch.searchBase", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH ),
-    PEOPLE_SEARCH_ENABLE_PUBLIC(
-            "peopleSearch.enablePublic", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.searchBase", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_ORGCHART(
-            "peopleSearch.enableOrgChart", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enableOrgChart", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_EXPORT(
-            "peopleSearch.enableExport", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enableExport", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_TEAM_MAILTO(
-            "peopleSearch.enableTeamMailto", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enableTeamMailto", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_PRINTING(
-            "peopleSearch.enablePrinting", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.enablePrinting", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS(
-            "peopleSearch.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
     PEOPLE_SEARCH_ENABLE_ADVANCED_SEARCH(
-            "peopleSearch.advancedSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.advancedSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
 
 
 
@@ -1231,13 +1239,12 @@ public enum PwmSetting
 
     // deprecated 2019-01-20
     PEOPLE_SEARCH_DISPLAY_NAME(
-            "peopleSearch.displayName.user", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH ),
+            "peopleSearch.displayName.user", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH_PROFILE ),
 
     // deprecated 2019-01-20
     HELPDESK_DETAIL_DISPLAY_NAME(
             "helpdesk.displayName", PwmSettingSyntax.STRING, PwmSettingCategory.HELPDESK_BASE ),
 
-
     // deprecated 2018-12-05
     REPORTING_SEARCH_FILTER(
             "reporting.ldap.searchFilter", PwmSettingSyntax.STRING, PwmSettingCategory.REPORTING ),

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

@@ -146,6 +146,8 @@ public enum PwmSettingCategory
     FORGOTTEN_USERNAME( MODULES_PUBLIC ),
 
     ACTIVATION( MODULES_PUBLIC ),
+    ACTIVATION_SETTINGS( ACTIVATION ),
+    ACTIVATION_PROFILE( ACTIVATION ),
 
     NEWUSER( MODULES_PUBLIC ),
     NEWUSER_SETTINGS( NEWUSER ),
@@ -157,7 +159,10 @@ public enum PwmSettingCategory
 
     GUEST( MODULES_PRIVATE ),
     SHORTCUT( MODULES_PRIVATE ),
+
     PEOPLE_SEARCH( MODULES_PRIVATE ),
+    PEOPLE_SEARCH_SETTINGS( PEOPLE_SEARCH ),
+    PEOPLE_SEARCH_PROFILE( PEOPLE_SEARCH ),
 
     HELPDESK( MODULES_PRIVATE ),
     HELPDESK_PROFILE( HELPDESK ),

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

@@ -32,6 +32,7 @@ import javax.xml.transform.stream.StreamSource;
 import javax.xml.validation.Schema;
 import javax.xml.validation.SchemaFactory;
 import javax.xml.validation.Validator;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.time.Instant;
@@ -63,8 +64,7 @@ public class PwmSettingXml
         final XmlDocument docRefCopy = xmlDocCache.get();
         if ( docRefCopy == null )
         {
-            final InputStream inputStream = PwmSetting.class.getClassLoader().getResourceAsStream( SETTING_XML_FILENAME );
-            try
+            try ( InputStream inputStream = PwmSetting.class.getClassLoader().getResourceAsStream( SETTING_XML_FILENAME ) )
             {
                 final Instant startTime = Instant.now();
                 final XmlDocument newDoc = XmlFactory.getFactory().parseXml( inputStream );
@@ -75,7 +75,7 @@ public class PwmSettingXml
 
                 return newDoc;
             }
-            catch ( PwmUnrecoverableException e )
+            catch ( IOException | PwmUnrecoverableException e )
             {
                 throw new IllegalStateException( "error parsing " + SETTING_XML_FILENAME + ": " + e.getMessage() );
             }

+ 59 - 0
server/src/main/java/password/pwm/config/profile/ActivateUserProfile.java

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

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

@@ -284,7 +284,7 @@ public class ChallengeProfile implements Profile, Serializable
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         throw new UnsupportedOperationException();
     }

+ 11 - 9
server/src/main/java/password/pwm/config/profile/DeleteAccountProfile.java

@@ -29,20 +29,13 @@ import java.util.Map;
 
 public class DeleteAccountProfile extends AbstractProfile implements Profile
 {
-    private static final ProfileType PROFILE_TYPE = ProfileType.DeleteAccount;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.DeleteAccount;
 
     protected DeleteAccountProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static DeleteAccountProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new DeleteAccountProfile( identifier, valueMap );
-
-    }
-
     @Override
     public String getDisplayName( final Locale locale )
     {
@@ -50,8 +43,17 @@ public class DeleteAccountProfile extends AbstractProfile implements Profile
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
+
+    public static class DeleteAccountProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new DeleteAccountProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 11 - 8
server/src/main/java/password/pwm/config/profile/EmailServerProfile.java

@@ -30,21 +30,15 @@ import java.util.Map;
 public class EmailServerProfile extends AbstractProfile
 {
 
-    private static final ProfileType PROFILE_TYPE = ProfileType.EmailServers;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.EmailServers;
 
     protected EmailServerProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static EmailServerProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new EmailServerProfile( identifier, valueMap );
-    }
-
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
@@ -55,4 +49,13 @@ public class EmailServerProfile extends AbstractProfile
         final String value = this.readSettingAsLocalizedString( PwmSetting.EMAIL_SERVERS, locale );
         return value != null && !value.isEmpty() ? value : this.getIdentifier();
     }
+
+    public static class EmailServerProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new EmailServerProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

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

@@ -33,7 +33,7 @@ import java.util.Set;
 public class ForgottenPasswordProfile extends AbstractProfile
 {
 
-    private static final ProfileType PROFILE_TYPE = ProfileType.ForgottenPassword;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.ForgottenPassword;
 
     private Set<IdentityVerificationMethod> requiredRecoveryVerificationMethods;
     private Set<IdentityVerificationMethod> optionalRecoveryVerificationMethods;
@@ -43,23 +43,14 @@ public class ForgottenPasswordProfile extends AbstractProfile
         super( identifier, storedValueMap );
     }
 
-
     @Override
     public String getDisplayName( final Locale locale )
     {
         return null;
     }
 
-    public static ForgottenPasswordProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new
-                ForgottenPasswordProfile( identifier, valueMap );
-
-    }
-
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
@@ -93,4 +84,13 @@ public class ForgottenPasswordProfile extends AbstractProfile
         final VerificationMethodValue.VerificationMethodSettings verificationMethodSettings = ( VerificationMethodValue.VerificationMethodSettings ) configValue.toNativeObject();
         return verificationMethodSettings.getMinOptionalRequired();
     }
+
+    public static class ForgottenPasswordProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new ForgottenPasswordProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 11 - 9
server/src/main/java/password/pwm/config/profile/HelpdeskProfile.java

@@ -35,20 +35,13 @@ import java.util.Set;
 
 public class HelpdeskProfile extends AbstractProfile implements Profile
 {
-
-    private static final ProfileType PROFILE_TYPE = ProfileType.Helpdesk;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.Helpdesk;
 
     protected HelpdeskProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static HelpdeskProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new HelpdeskProfile( identifier, valueMap );
-    }
-
     @Override
     public String getDisplayName( final Locale locale )
     {
@@ -56,7 +49,7 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
@@ -73,4 +66,13 @@ public class HelpdeskProfile extends AbstractProfile implements Profile
     {
         return readVerificationMethods( PwmSetting.HELPDESK_VERIFICATION_METHODS, VerificationMethodValue.EnabledState.required );
     }
+
+    public static class HelpdeskProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new HelpdeskProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 19 - 9
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -28,9 +28,8 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.StoredValue;
-import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.cache.CacheKey;
@@ -50,17 +49,13 @@ public class LdapProfile extends AbstractProfile implements Profile
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LdapProfile.class );
 
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.LdapProfile;
+
     protected LdapProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static LdapProfile makeFromStoredConfiguration( final StoredConfigurationImpl storedConfiguration, final String profileID )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = AbstractProfile.makeValueMap( storedConfiguration, profileID, PwmSettingCategory.LDAP_PROFILE );
-        return new LdapProfile( profileID, valueMap );
-    }
-
     public Map<String, String> getSelectableContexts(
             final PwmApplication pwmApplication
     )
@@ -120,7 +115,7 @@ public class LdapProfile extends AbstractProfile implements Profile
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         throw new UnsupportedOperationException();
     }
@@ -210,4 +205,19 @@ public class LdapProfile extends AbstractProfile implements Profile
 
         return null;
     }
+
+    public static class LdapProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new LdapProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return "LDAPProfile:" + this.getIdentifier();
+    }
 }

+ 12 - 10
server/src/main/java/password/pwm/config/profile/NewUserProfile.java

@@ -42,10 +42,10 @@ import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 
-public class NewUserProfile extends AbstractProfile
+public class NewUserProfile extends AbstractProfile implements Profile
 {
 
-    private static final ProfileType PROFILE_TYPE = ProfileType.NewUser;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.NewUser;
     public static final String TEST_USER_CONFIG_VALUE = "TESTUSER";
 
     private Instant newUserPasswordPolicyCacheTime;
@@ -56,15 +56,8 @@ public class NewUserProfile extends AbstractProfile
         super( identifier, storedValueMap );
     }
 
-    public static NewUserProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new NewUserProfile( identifier, valueMap );
-
-    }
-
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
@@ -173,4 +166,13 @@ public class NewUserProfile extends AbstractProfile
         }
         return TimeDuration.of( newUserDuration, TimeDuration.Unit.SECONDS );
     }
+
+    public static class NewUserProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new NewUserProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 60 - 0
server/src/main/java/password/pwm/config/profile/PeopleSearchProfile.java

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

+ 8 - 3
server/src/main/java/password/pwm/config/profile/Profile.java

@@ -20,19 +20,24 @@
 
 package password.pwm.config.profile;
 
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.value.data.UserPermission;
 
-import java.io.Serializable;
 import java.util.List;
 import java.util.Locale;
 
-public interface Profile extends Serializable
+public interface Profile
 {
-    ProfileType profileType( );
+    ProfileDefinition profileType( );
 
     String getIdentifier( );
 
     String getDisplayName( Locale locale );
 
     List<UserPermission> getPermissionMatches( );
+
+    interface ProfileFactory
+    {
+        Profile makeFromStoredConfiguration( StoredConfiguration storedConfiguration, String identifier );
+    }
 }

+ 159 - 0
server/src/main/java/password/pwm/config/profile/ProfileDefinition.java

@@ -0,0 +1,159 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.config.profile;
+
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+
+public enum ProfileDefinition
+{
+    Helpdesk(
+            Type.AUTHENTICATED,
+            HelpdeskProfile.class,
+            HelpdeskProfile.HelpdeskProfileFactory.class,
+            PwmSettingCategory.HELPDESK_PROFILE,
+            PwmSetting.HELPDESK_PROFILE_QUERY_MATCH ),
+    ForgottenPassword(
+            Type.PUBLIC,
+            ForgottenPasswordProfile.class,
+            ForgottenPasswordProfile.ForgottenPasswordProfileFactory.class,
+            PwmSettingCategory.RECOVERY_PROFILE,
+            PwmSetting.RECOVERY_PROFILE_QUERY_MATCH ),
+    NewUser(
+            Type.PUBLIC,
+            NewUserProfile.class,
+            NewUserProfile.NewUserProfileFactory.class,
+            PwmSettingCategory.NEWUSER_PROFILE,
+            null ),
+    UpdateAttributes(
+            Type.AUTHENTICATED,
+            UpdateProfileProfile.class,
+            UpdateProfileProfile.UpdateProfileProfileFactory.class,
+            PwmSettingCategory.UPDATE_PROFILE,
+            PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
+    ActivateUser(
+            Type.PUBLIC,
+            ActivateUserProfile.class,
+            ActivateUserProfile.UserActivationProfileFactory.class,
+            PwmSettingCategory.ACTIVATION_PROFILE,
+            PwmSetting.ACTIVATE_USER_QUERY_MATCH ),
+    DeleteAccount(
+            Type.AUTHENTICATED,
+            DeleteAccountProfile.class,
+            DeleteAccountProfile.DeleteAccountProfileFactory.class,
+            PwmSettingCategory.DELETE_ACCOUNT_PROFILE,
+            PwmSetting.DELETE_ACCOUNT_PERMISSION ),
+    SetupOTPProfile(
+            Type.AUTHENTICATED,
+            SetupOtpProfile.class,
+            SetupOtpProfile.SetupOtpProfileFactory.class,
+            PwmSettingCategory.OTP_PROFILE,
+            PwmSetting.OTP_SETUP_USER_PERMISSION ),
+    PeopleSearch(
+            Type.AUTHENTICATED,
+            PeopleSearchProfile.class,
+            PeopleSearchProfile.PeopleSearchProfileFactory.class,
+            PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
+            PwmSetting.PEOPLE_SEARCH_QUERY_MATCH ),
+    PeopleSearchPublic(
+            Type.PUBLIC,
+            PeopleSearchProfile.class,
+            PeopleSearchProfile.PeopleSearchProfileFactory.class,
+            PwmSettingCategory.PEOPLE_SEARCH_PROFILE,
+            null ),
+    EmailServers(
+            Type.SERVICE,
+            EmailServerProfile.class,
+            EmailServerProfile.EmailServerProfileFactory.class,
+            PwmSettingCategory.EMAIL_SERVERS,
+            null ),
+    PasswordPolicy(
+            Type.SERVICE,
+            PwmPasswordPolicy.class,
+            null,
+            PwmSettingCategory.PASSWORD_POLICY,
+            PwmSetting.PASSWORD_POLICY_QUERY_MATCH ),
+    LdapProfile(
+            Type.SERVICE,
+            LdapProfile.class,
+            LdapProfile.LdapProfileFactory.class,
+            PwmSettingCategory.LDAP_PROFILE,
+            null ),
+    ChallengeProfile(
+            Type.SERVICE,
+            ChallengeProfile.class,
+            null,
+            PwmSettingCategory.CHALLENGE_POLICY,
+            PwmSetting.CHALLENGE_POLICY_QUERY_MATCH ),;
+
+    private final Type type;
+    private final Class<? extends Profile> profileImplClass;
+    private final Class<? extends Profile.ProfileFactory> profileFactoryClass;
+    private final PwmSettingCategory category;
+    private final PwmSetting queryMatch;
+
+    enum Type
+    {
+        PUBLIC,
+        AUTHENTICATED,
+        SERVICE,
+    }
+
+    ProfileDefinition(
+            final Type type,
+            final Class<? extends Profile> profileImplClass,
+            final Class<? extends Profile.ProfileFactory> profileFactoryClass,
+            final PwmSettingCategory category,
+            final PwmSetting queryMatch
+    )
+    {
+        this.type = type;
+        this.profileImplClass = profileImplClass;
+        this.profileFactoryClass = profileFactoryClass;
+        this.category = category;
+        this.queryMatch = queryMatch;
+    }
+
+    public boolean isAuthenticated( )
+    {
+        return type == Type.AUTHENTICATED;
+    }
+
+    public PwmSettingCategory getCategory( )
+    {
+        return category;
+    }
+
+    public PwmSetting getQueryMatch( )
+    {
+        return queryMatch;
+    }
+
+    public Class<? extends Profile> getProfileImplClass()
+    {
+        return profileImplClass;
+    }
+
+    public Class<? extends Profile.ProfileFactory> getProfileFactoryClass()
+    {
+        return profileFactoryClass;
+    }
+}

+ 0 - 62
server/src/main/java/password/pwm/config/profile/ProfileType.java

@@ -1,62 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2019 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package password.pwm.config.profile;
-
-import password.pwm.config.PwmSetting;
-import password.pwm.config.PwmSettingCategory;
-
-public enum ProfileType
-{
-    Helpdesk            ( true,  PwmSettingCategory.HELPDESK_PROFILE,    PwmSetting.HELPDESK_PROFILE_QUERY_MATCH ),
-    ForgottenPassword   ( false, PwmSettingCategory.RECOVERY_PROFILE,    PwmSetting.RECOVERY_PROFILE_QUERY_MATCH ),
-    NewUser             ( false, PwmSettingCategory.NEWUSER_PROFILE,     null ),
-    UpdateAttributes    ( true,  PwmSettingCategory.UPDATE_PROFILE,      PwmSetting.UPDATE_PROFILE_QUERY_MATCH ),
-    DeleteAccount       ( true,  PwmSettingCategory.DELETE_ACCOUNT_PROFILE, PwmSetting.DELETE_ACCOUNT_PERMISSION ),
-    SetupOTPProfile     ( true, PwmSettingCategory.OTP_PROFILE, PwmSetting.OTP_SETUP_USER_PERMISSION ),
-    EmailServers        ( true, PwmSettingCategory.EMAIL_SERVERS, null ),;
-
-    
-    private final boolean authenticated;
-    private final PwmSettingCategory category;
-    private final PwmSetting queryMatch;
-
-    ProfileType( final boolean authenticated, final PwmSettingCategory category, final PwmSetting queryMatch )
-    {
-        this.authenticated = authenticated;
-        this.category = category;
-        this.queryMatch = queryMatch;
-    }
-
-    public boolean isAuthenticated( )
-    {
-        return authenticated;
-    }
-
-    public PwmSettingCategory getCategory( )
-    {
-        return category;
-    }
-
-    public PwmSetting getQueryMatch( )
-    {
-        return queryMatch;
-    }
-}

+ 34 - 2
server/src/main/java/password/pwm/config/profile/ProfileUtility.java

@@ -27,26 +27,58 @@ import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.CommonValues;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 public class ProfileUtility
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ProfileUtility.class );
 
+    public static Optional<String> discoverProfileIDforUser(
+            final CommonValues commonValues,
+            final UserIdentity userIdentity,
+            final ProfileDefinition profileDefinition
+    )
+            throws PwmUnrecoverableException
+    {
+        final String profileID = discoverProfileIDforUser( commonValues.getPwmApplication(), commonValues.getSessionLabel(), userIdentity, profileDefinition );
+        return Optional.ofNullable( profileID );
+    }
+
+    public static <T extends Profile> T profileForUser(
+            final CommonValues commonValues,
+            final UserIdentity userIdentity,
+            final ProfileDefinition profileDefinition,
+            final Class<T> classOfT
+    )
+            throws PwmUnrecoverableException
+    {
+        final Optional<String> profileID = discoverProfileIDforUser( commonValues, userIdentity, profileDefinition );
+        if ( !profileID.isPresent() )
+        {
+            throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "profile of type " + profileDefinition + " is required but not assigned" );
+        }
+        final Profile profileImpl = commonValues.getConfig().profileMap( profileDefinition ).get( profileID.get() );
+        return ( T ) profileImpl;
+    }
+
+
     public static String discoverProfileIDforUser(
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
-            final ProfileType profileType
+            final ProfileDefinition profileDefinition
     )
             throws PwmUnrecoverableException
     {
-        final Map<String, Profile> profileMap = pwmApplication.getConfig().profileMap( profileType );
+        final Map<String, Profile> profileMap = pwmApplication.getConfig().profileMap( profileDefinition );
         for ( final Profile profile : profileMap.values() )
         {
             final List<UserPermission> queryMatches = profile.getPermissionMatches();

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

@@ -332,7 +332,7 @@ public class PwmPasswordPolicy implements Profile, Serializable
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         throw new UnsupportedOperationException();
     }

+ 3 - 3
server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java

@@ -283,21 +283,21 @@ public enum PwmPasswordRule
             false ),
 
     AllowNonAlpha(
-            ChaiPasswordRule.AllowNonAlpha,
+            null,
             PwmSetting.PASSWORD_POLICY_ALLOW_NON_ALPHA,
             ChaiPasswordRule.AllowNonAlpha.getRuleType(),
             ChaiPasswordRule.AllowNonAlpha.getDefaultValue(),
             false ),
 
     MinimumNonAlpha(
-            ChaiPasswordRule.MinimumNonAlpha,
+            null,
             PwmSetting.PASSWORD_POLICY_MINIMUM_NON_ALPHA,
             ChaiPasswordRule.RuleType.MIN,
             "0",
             false ),
 
     MaximumNonAlpha(
-            ChaiPasswordRule.MaximumNonAlpha,
+            null,
             PwmSetting.PASSWORD_POLICY_MAXIMUM_NON_ALPHA,
             ChaiPasswordRule.RuleType.MAX,
             "0",

+ 10 - 8
server/src/main/java/password/pwm/config/profile/SetupOtpProfile.java

@@ -29,19 +29,13 @@ import java.util.Map;
 
 public class SetupOtpProfile extends AbstractProfile
 {
-    private static final ProfileType PROFILE_TYPE = ProfileType.SetupOTPProfile;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.SetupOTPProfile;
 
     protected SetupOtpProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static SetupOtpProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new SetupOtpProfile( identifier, valueMap );
-    }
-
     @Override
     public String getDisplayName( final Locale locale )
     {
@@ -49,9 +43,17 @@ public class SetupOtpProfile extends AbstractProfile
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
 
+    public static class SetupOtpProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new SetupOtpProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 11 - 9
server/src/main/java/password/pwm/config/profile/UpdateProfileProfile.java

@@ -32,20 +32,13 @@ import java.util.Map;
 public class UpdateProfileProfile extends AbstractProfile implements Profile
 {
 
-    private static final ProfileType PROFILE_TYPE = ProfileType.UpdateAttributes;
+    private static final ProfileDefinition PROFILE_TYPE = ProfileDefinition.UpdateAttributes;
 
     protected UpdateProfileProfile( final String identifier, final Map<PwmSetting, StoredValue> storedValueMap )
     {
         super( identifier, storedValueMap );
     }
 
-    public static UpdateProfileProfile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
-    {
-        final Map<PwmSetting, StoredValue> valueMap = makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() );
-        return new UpdateProfileProfile( identifier, valueMap );
-
-    }
-
     @Override
     public String getDisplayName( final Locale locale )
     {
@@ -53,7 +46,7 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
     }
 
     @Override
-    public ProfileType profileType( )
+    public ProfileDefinition profileType( )
     {
         return PROFILE_TYPE;
     }
@@ -79,4 +72,13 @@ public class UpdateProfileProfile extends AbstractProfile implements Profile
         }
         return TimeDuration.of( duration, TimeDuration.Unit.SECONDS );
     }
+
+    public static class UpdateProfileProfileFactory implements ProfileFactory
+    {
+        @Override
+        public Profile makeFromStoredConfiguration( final StoredConfiguration storedConfiguration, final String identifier )
+        {
+            return new UpdateProfileProfile( identifier, makeValueMap( storedConfiguration, identifier, PROFILE_TYPE.getCategory() ) );
+        }
+    }
 }

+ 4 - 1
server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -183,13 +183,16 @@ public class StoredConfigurationImpl implements StoredConfiguration
     }
 
     public void resetAllPasswordValues( final String comment )
+            throws PwmUnrecoverableException
     {
         for ( final Iterator<SettingValueRecord> settingValueRecordIterator = new StoredValueIterator( false ); settingValueRecordIterator.hasNext(); )
         {
             final SettingValueRecord settingValueRecord = settingValueRecordIterator.next();
             if ( settingValueRecord.getSetting().getSyntax() == PwmSettingSyntax.PASSWORD )
             {
-                this.resetSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile(), null );
+                final ValueMetaData valueMetaData = this.readSettingMetadata( settingValueRecord.getSetting(), settingValueRecord.getProfile() );
+                final PasswordValue passwordValue = new PasswordValue( new PasswordData( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT ) );
+                this.writeSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile(), passwordValue, valueMetaData.getUserIdentity() );
                 if ( comment != null && !comment.isEmpty() )
                 {
                     final XmlElement settingElement = xmlHelper.xpathForSetting( settingValueRecord.getSetting(), settingValueRecord.getProfile() );

+ 0 - 23
server/src/main/java/password/pwm/error/ErrorInformation.java

@@ -27,12 +27,7 @@ import password.pwm.http.PwmSession;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
 import java.util.Locale;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 /**
  * An ErrorInformation is a package of error data generated within PWM.  Error information includes an error code
@@ -176,23 +171,5 @@ public class ErrorInformation implements Serializable
             return this;
         }
         return new ErrorInformation( pwmError, this.getDetailedErrorMsg() );
-
-    }
-
-    public static boolean listsContainSameErrors( final List<ErrorInformation> errorInformation1, final List<ErrorInformation> errorInformation2 )
-    {
-        Objects.requireNonNull( errorInformation1 );
-        Objects.requireNonNull( errorInformation2 );
-        return extractErrorSet( errorInformation1 ).equals( extractErrorSet( errorInformation2 ) );
-    }
-
-    private static Set<PwmError> extractErrorSet( final List<ErrorInformation> errors )
-    {
-        if ( errors != null )
-        {
-            return errors.stream().map( ErrorInformation::getError ).collect( Collectors.toSet() );
-        }
-
-        return Collections.emptySet();
     }
 }

+ 13 - 9
server/src/main/java/password/pwm/health/ApplianceStatusChecker.java

@@ -29,14 +29,13 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.X509Utils;
 
 import java.io.File;
 import java.io.IOException;
@@ -89,12 +88,17 @@ public class ApplianceStatusChecker implements HealthChecker
         final Map<String, String> requestHeaders = Collections.singletonMap( "sspr-authorization-token", getApplianceAccessToken( pwmApplication ) );
 
         final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
-                .trustManager( new X509Utils.PromiscuousTrustManager( SessionLabel.HEALTH_SESSION_LABEL ) )
+                .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
                 .build();
 
-        final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmApplication, SessionLabel.HEALTH_SESSION_LABEL, pwmHttpClientConfiguration );
-        final PwmHttpClientRequest pwmHttpClientRequest = new PwmHttpClientRequest( HttpMethod.GET, url, null, requestHeaders );
-        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+        final PwmHttpClient pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( pwmHttpClientConfiguration );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
+                .method( HttpMethod.GET )
+                .url( url )
+                .headers( requestHeaders )
+                .build();
+
+        final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, SessionLabel.HEALTH_SESSION_LABEL );
 
         LOGGER.trace( SessionLabel.HEALTH_SESSION_LABEL, () -> "https response from appliance server request: " + response.getBody() );
 

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

@@ -36,6 +36,7 @@ import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.config.profile.ActivateUserProfile;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
@@ -459,8 +460,9 @@ public class ConfigurationChecker implements HealthChecker
 
             final List<HealthRecord> records = new ArrayList<>();
 
+            for ( final ActivateUserProfile activationProfile : config.getUserActivationProfiles().values() )
             {
-                final MessageSendMethod method = config.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
+                final MessageSendMethod method = activationProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
                 if ( deprecatedMethods.contains( method ) )
                 {
                     records.add( HealthRecord.forMessage( HealthMessage.Config_InvalidSendMethod,

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

@@ -112,9 +112,10 @@ public class LDAPHealthChecker implements HealthChecker
             returnRecords.addAll( profileRecords );
         }
 
-        for ( final LdapProfile ldapProfile : pwmApplication.getLdapConnectionService().getLastLdapFailure().keySet() )
+        for ( final Map.Entry<String, ErrorInformation> entry : pwmApplication.getLdapConnectionService().getLastLdapFailure().entrySet() )
         {
-            final ErrorInformation errorInfo = pwmApplication.getLdapConnectionService().getLastLdapFailure().get( ldapProfile );
+            final ErrorInformation errorInfo = entry.getValue();
+            final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get( entry.getKey() );
             if ( errorInfo != null )
             {
                 final TimeDuration errorAge = TimeDuration.fromCurrent( errorInfo.getDate() );

+ 38 - 29
server/src/main/java/password/pwm/http/HttpContentType.java

@@ -28,45 +28,51 @@ import java.util.List;
 
 public enum HttpContentType
 {
-    json( "application/json", PwmConstants.DEFAULT_CHARSET ),
-    zip( "application/zip" ),
-    gzip( "application/gzip" ),
-    xml( "text/xml", PwmConstants.DEFAULT_CHARSET ),
-    csv( "text/csv", PwmConstants.DEFAULT_CHARSET ),
-    javascript( "text/javascript", PwmConstants.DEFAULT_CHARSET ),
-    plain( "text/plain", PwmConstants.DEFAULT_CHARSET ),
-    html( "text/html", PwmConstants.DEFAULT_CHARSET ),
-    form( "application/x-www-form-urlencoded", PwmConstants.DEFAULT_CHARSET ),
-    png( "image/png" ),
-    octetstream( "application/octet-stream" ),;
-
-    private final String mimeType;
-    private final String charset;
-
-    HttpContentType( final String mimeType, final Charset charset )
-    {
-        this.mimeType = mimeType;
-        this.charset = charset.name();
-    }
+    json( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "application/json", "application/javascript" ),
+    zip( HttpEntityDataType.ByteArray, null, "application/zip" ),
+    gzip( HttpEntityDataType.ByteArray, null, "application/gzip" ),
+    xml( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "text/xml" ),
+    csv( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "text/csv" ),
+    javascript( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "text/javascript" ),
+    plain( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "text/plain" ),
+    html( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "text/html" ),
+    form( HttpEntityDataType.String, PwmConstants.DEFAULT_CHARSET, "application/x-www-form-urlencoded" ),
+    png( HttpEntityDataType.ByteArray, null, "image/png" ),
+    jpg( HttpEntityDataType.ByteArray, null, "image/jpg", "image/jpeg" ),
+    bmp( HttpEntityDataType.ByteArray, null, "image/bmp" ),
+    webp( HttpEntityDataType.ByteArray, null, "image/webp" ),
+    octetstream( HttpEntityDataType.ByteArray, null, "application/octet-stream" ),;
+
+    private final String[] mimeType;
+    private final Charset charset;
+    private final HttpEntityDataType dataType;
 
-    HttpContentType( final String mimeType )
+    HttpContentType( final HttpEntityDataType dataType, final Charset charset, final String... mimeType )
     {
         this.mimeType = mimeType;
-        this.charset = null;
+        this.dataType = dataType;
+        this.charset = charset;
     }
 
-    public String getHeaderValue( )
+    public String getHeaderValueWithEncoding( )
     {
-        if ( charset == null )
+        String output = getMimeType();
+        if ( charset != null )
         {
-            return mimeType;
+            output += "; charset=" + charset.name();
         }
-        return mimeType + "; charset=" + charset;
+
+        return output;
     }
 
     public String getMimeType( )
     {
-        return this.mimeType;
+        return this.mimeType[0];
+    }
+
+    public HttpEntityDataType getDataType()
+    {
+        return dataType;
     }
 
     public static HttpContentType fromContentTypeHeader( final String headerValue, final HttpContentType anyMatch )
@@ -86,9 +92,12 @@ public enum HttpContentType
 
             for ( final HttpContentType httpContentType : HttpContentType.values() )
             {
-                if ( mimeValue.equalsIgnoreCase( httpContentType.getMimeType() ) )
+                for ( final String type : httpContentType.mimeType )
                 {
-                    return httpContentType;
+                    if ( mimeValue.equalsIgnoreCase( type ) )
+                    {
+                        return httpContentType;
+                    }
                 }
             }
         }

+ 27 - 0
server/src/main/java/password/pwm/http/HttpEntityDataType.java

@@ -0,0 +1,27 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 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;
+
+public enum HttpEntityDataType
+{
+    String,
+    ByteArray,
+}

+ 34 - 15
server/src/main/java/password/pwm/http/IdleTimeoutCalculator.java

@@ -31,7 +31,8 @@ import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.PeopleSearchProfile;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.ldap.UserInfo;
@@ -40,6 +41,7 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
+import java.util.Optional;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -72,12 +74,17 @@ public class IdleTimeoutCalculator
 
             if ( configuration.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
             {
-                final long peopleSearchIdleTimeout = configuration.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
-                if ( peopleSearchIdleTimeout > 0 )
+                final Optional<PeopleSearchProfile> optionalPeopleSearchProfile = configuration.getPublicPeopleSearchProfile();
+                if ( optionalPeopleSearchProfile.isPresent() )
                 {
-                    results.add( new MaxIdleTimeoutResult(
-                            MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, null ),
-                            TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS ) ) );
+                    final PeopleSearchProfile publicProfile = optionalPeopleSearchProfile.get();
+                    final long peopleSearchIdleTimeout = publicProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
+                    if ( peopleSearchIdleTimeout > 0 )
+                    {
+                        results.add( new MaxIdleTimeoutResult(
+                                MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, publicProfile.getIdentifier() ),
+                                TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS ) ) );
+                    }
                 }
             }
 
@@ -111,7 +118,7 @@ public class IdleTimeoutCalculator
 
         if ( configuration.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE ) )
         {
-            final String helpdeskProfileID = userInfo.getProfileIDs().get( ProfileType.Helpdesk );
+            final String helpdeskProfileID = userInfo.getProfileIDs().get( ProfileDefinition.Helpdesk );
             if ( !StringUtil.isEmpty( helpdeskProfileID ) )
             {
                 final HelpdeskProfile helpdeskProfile = configuration.getHelpdeskProfiles().get( helpdeskProfileID );
@@ -124,12 +131,18 @@ public class IdleTimeoutCalculator
 
         if ( configuration.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
         {
-            final long peopleSearchIdleTimeout = configuration.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
-            if ( peopleSearchIdleTimeout > 0 )
+            final String peopleSearchID = userInfo.getProfileIDs().get( ProfileDefinition.PeopleSearch );
+            if ( !StringUtil.isEmpty( peopleSearchID ) )
             {
-                results.add( new MaxIdleTimeoutResult(
-                        MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, null ),
-                        TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS ) ) );
+                final PeopleSearchProfile peopleSearchProfile = configuration.getPeopleSearchProfiles().get( peopleSearchID );
+                final long peopleSearchIdleTimeout = peopleSearchProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
+                if ( peopleSearchIdleTimeout > 0 )
+                {
+                    results.add( new MaxIdleTimeoutResult(
+                            MaxIdleTimeoutResult.reasonFor( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS, peopleSearchID ),
+                            TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS ) ) );
+                }
+
             }
         }
 
@@ -169,7 +182,12 @@ public class IdleTimeoutCalculator
         return idleTimeoutForRequest( pwmRequest.getURL(), pwmRequest.getPwmApplication(), pwmRequest.getPwmSession() );
     }
 
-    public static TimeDuration idleTimeoutForRequest( final PwmURL pwmURL, final PwmApplication pwmApplication, final PwmSession pwmSession ) throws PwmUnrecoverableException
+    public static TimeDuration idleTimeoutForRequest(
+            final PwmURL pwmURL,
+            final PwmApplication pwmApplication,
+            final PwmSession pwmSession
+    )
+            throws PwmUnrecoverableException
     {
         if ( pwmURL.isResourceURL() )
         {
@@ -201,9 +219,10 @@ public class IdleTimeoutCalculator
                         && pwmURL.isPrivateUrl()
                 )
         {
-            if ( config.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
+            final PeopleSearchProfile peopleSearchProfile = pwmSession.getSessionManager().getPeopleSearchProfile( pwmApplication );
+            if ( peopleSearchProfile != null )
             {
-                final long peopleSearchIdleTimeout = config.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
+                final long peopleSearchIdleTimeout = peopleSearchProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS );
                 if ( peopleSearchIdleTimeout > 0 )
                 {
                     return TimeDuration.of( peopleSearchIdleTimeout, TimeDuration.Unit.SECONDS );

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

@@ -35,7 +35,7 @@ public enum JspUrl
     ADMIN_LOGVIEW( "admin-logview.jsp" ),
     ADMIN_URLREFERENCE( "admin-urlreference.jsp" ),
     ADMIN_DEBUG( "admin-user-debug.jsp" ),
-    ACTIVATE_USER( "activateuser.jsp" ),
+    ACTIVATE_USER_SEARCH( "activateuser-search.jsp" ),
     ACTIVATE_USER_AGREEMENT( "activateuser-agreement.jsp" ),
     ACTIVATE_USER_TOKEN_CHOICE( "activateuser-tokenchoice.jsp" ),
     ACTIVATE_USER_ENTER_CODE( "activateuser-entercode.jsp" ),

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

@@ -124,7 +124,7 @@ public class PwmHttpResponseWrapper
 
     public void setContentType( final HttpContentType contentType )
     {
-        this.getHttpServletResponse().setContentType( contentType.getHeaderValue() );
+        this.getHttpServletResponse().setContentType( contentType.getHeaderValueWithEncoding() );
     }
 
     public PrintWriter getWriter( )

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

@@ -220,7 +220,7 @@ public class PwmResponse extends PwmHttpResponseWrapper
         preCommitActions();
         final HttpServletResponse resp = this.getHttpServletResponse();
         final String outputString = restResultBean.toJson();
-        resp.setContentType( HttpContentType.json.getHeaderValue() );
+        resp.setContentType( HttpContentType.json.getHeaderValueWithEncoding() );
         resp.getWriter().print( outputString );
         resp.getWriter().close();
     }

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

@@ -34,6 +34,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Pattern;
 
 public class PwmURL
@@ -48,7 +49,8 @@ public class PwmURL
             final String contextPath
     )
     {
-        this.uri = uri;
+        Objects.requireNonNull( uri );
+        this.uri = uri.normalize();
         this.contextPath = contextPath;
     }
 

+ 15 - 9
server/src/main/java/password/pwm/http/SessionManager.java

@@ -29,8 +29,9 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.DeleteAccountProfile;
 import password.pwm.config.profile.HelpdeskProfile;
+import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.profile.Profile;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.config.profile.UpdateProfileProfile;
 import password.pwm.config.value.data.UserPermission;
@@ -254,37 +255,42 @@ public class SessionManager
         return MacroMachine.forUser( pwmApplication, pwmSession.getLabel(), userInfoBean, pwmSession.getLoginInfoBean() );
     }
 
-    public Profile getProfile( final PwmApplication pwmApplication, final ProfileType profileType ) throws PwmUnrecoverableException
+    public Profile getProfile( final PwmApplication pwmApplication, final ProfileDefinition profileDefinition ) throws PwmUnrecoverableException
     {
-        if ( profileType.isAuthenticated() && !pwmSession.isAuthenticated() )
+        if ( profileDefinition.isAuthenticated() && !pwmSession.isAuthenticated() )
         {
             return null;
         }
-        final String profileID = pwmSession.getUserInfo().getProfileIDs().get( profileType );
+        final String profileID = pwmSession.getUserInfo().getProfileIDs().get( profileDefinition );
         if ( profileID != null )
         {
-            return pwmApplication.getConfig().profileMap( profileType ).get( profileID );
+            return pwmApplication.getConfig().profileMap( profileDefinition ).get( profileID );
         }
         return null;
     }
 
     public HelpdeskProfile getHelpdeskProfile( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     {
-        return ( HelpdeskProfile ) getProfile( pwmApplication, ProfileType.Helpdesk );
+        return ( HelpdeskProfile ) getProfile( pwmApplication, ProfileDefinition.Helpdesk );
     }
 
     public SetupOtpProfile getSetupOTPProfile( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     {
-        return ( SetupOtpProfile ) getProfile( pwmApplication, ProfileType.SetupOTPProfile );
+        return ( SetupOtpProfile ) getProfile( pwmApplication, ProfileDefinition.SetupOTPProfile );
     }
 
     public UpdateProfileProfile getUpdateAttributeProfile( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     {
-        return ( UpdateProfileProfile ) getProfile( pwmApplication, ProfileType.UpdateAttributes );
+        return ( UpdateProfileProfile ) getProfile( pwmApplication, ProfileDefinition.UpdateAttributes );
+    }
+
+    public PeopleSearchProfile getPeopleSearchProfile( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
+    {
+        return ( PeopleSearchProfile ) getProfile( pwmApplication, ProfileDefinition.PeopleSearch );
     }
 
     public DeleteAccountProfile getSelfDeleteProfile( final PwmApplication pwmApplication ) throws PwmUnrecoverableException
     {
-        return ( DeleteAccountProfile ) getProfile( pwmApplication, ProfileType.DeleteAccount );
+        return ( DeleteAccountProfile ) getProfile( pwmApplication, ProfileDefinition.DeleteAccount );
     }
 }

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

@@ -52,6 +52,9 @@ public class ActivateUserBean extends PwmSessionBean
     @SerializedName( "td" )
     private TokenDestinationItem tokenDestination;
 
+    @SerializedName( "p" )
+    private String profileID;
+
 
     public Type getType( )
     {

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

@@ -27,6 +27,8 @@ public class ImmutableByteArray implements Serializable
 {
     private final byte[] bytes;
 
+    private static final ImmutableByteArray EMPTY = ImmutableByteArray.of( new byte[0] );
+
     private ImmutableByteArray( final byte[] bytes )
     {
         this.bytes = bytes == null ? null : Arrays.copyOf( bytes, bytes.length );
@@ -34,11 +36,28 @@ public class ImmutableByteArray implements Serializable
 
     public static ImmutableByteArray of( final byte[] bytes )
     {
-        return new ImmutableByteArray( bytes );
+        return bytes == null || bytes.length == 0
+                ? EMPTY
+                : new ImmutableByteArray( bytes );
     }
 
     public byte[] copyOf( )
     {
         return bytes == null ? null : Arrays.copyOf( bytes, bytes.length );
     }
+
+    public int size()
+    {
+        return bytes == null ? 0 : bytes.length;
+    }
+
+    public boolean isEmpty()
+    {
+        return bytes == null || bytes.length == 0;
+    }
+
+    public static ImmutableByteArray empty()
+    {
+        return EMPTY;
+    }
 }

+ 0 - 28
server/src/main/java/password/pwm/http/bean/UserSessionDataCacheBean.java

@@ -21,18 +21,14 @@
 package password.pwm.http.bean;
 
 import password.pwm.Permission;
-import password.pwm.util.PostChangePasswordAction;
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 public class UserSessionDataCacheBean implements Serializable
 {
     private Map<Permission, Permission.PermissionStatus> permissions = new HashMap<>();
-    private Map<String, PostChangePasswordAction> postChangePasswordActions = new HashMap<>();
 
     public void clearPermissions( )
     {
@@ -62,28 +58,4 @@ public class UserSessionDataCacheBean implements Serializable
     {
         this.permissions = permissions;
     }
-
-    public void addPostChangePasswordActions(
-            final String key,
-            final PostChangePasswordAction postChangePasswordAction
-    )
-    {
-        if ( postChangePasswordAction == null )
-        {
-            postChangePasswordActions.remove( key );
-        }
-        else
-        {
-            postChangePasswordActions.put( key, postChangePasswordAction );
-        }
-    }
-
-    public List<PostChangePasswordAction> removePostChangePasswordActions( )
-    {
-        final List<PostChangePasswordAction> copiedList = new ArrayList<>();
-        copiedList.addAll( postChangePasswordActions.values() );
-        postChangePasswordActions.clear();
-        return copiedList;
-    }
-
 }

+ 10 - 296
server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java

@@ -20,58 +20,34 @@
 
 package password.pwm.http.filter;
 
-import com.google.gson.annotations.SerializedName;
-import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
-import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
-import password.pwm.PwmConstants;
-import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
-import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
-import password.pwm.http.JspUrl;
 import password.pwm.http.ProcessStatus;
-import password.pwm.http.PwmHttpResponseWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmRequestAttribute;
-import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ConfigManagerBean;
-import password.pwm.svc.intruder.RecordType;
+import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.svc.sessiontrack.UserAgentUtils;
-import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.StringUtil;
-import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.secure.PwmHashAlgorithm;
-import password.pwm.util.secure.SecureEngine;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
-import java.io.Serializable;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 
 public class ConfigAccessFilter extends AbstractPwmFilter
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigAccessFilter.class );
 
-    private static final String COOKIE_NAME = PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN;
-    private static final PwmHttpResponseWrapper.CookiePath COOKIE_PATH = PwmHttpResponseWrapper.CookiePath.Private;
-
     @Override
-    void processFilter( final PwmApplicationMode mode, final PwmRequest pwmRequest, final PwmFilterChain filterChain ) throws PwmException, IOException, ServletException
+    void processFilter( final PwmApplicationMode mode, final PwmRequest pwmRequest, final PwmFilterChain filterChain )
+            throws PwmException, IOException, ServletException
     {
         final PwmApplicationMode appMode = pwmRequest.getPwmApplication().getApplicationMode();
         if ( appMode == PwmApplicationMode.NEW )
@@ -111,17 +87,15 @@ public class ConfigAccessFilter extends AbstractPwmFilter
     @Override
     boolean isInterested( final PwmApplicationMode mode, final PwmURL pwmURL )
     {
-        return pwmURL.isConfigManagerURL();
+        return true;
     }
 
-    private static ProcessStatus checkAuthentication(
+    public static ProcessStatus checkAuthentication(
             final PwmRequest pwmRequest,
             final ConfigManagerBean configManagerBean
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
         final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration();
 
@@ -132,117 +106,15 @@ public class ConfigAccessFilter extends AbstractPwmFilter
             return ProcessStatus.Continue;
         }
 
-        final boolean persistentLoginEnabled = persistentLoginEnabled( pwmRequest );
-
-        if ( persistentLoginEnabled )
-        {
-            final boolean persistentLoginPassed = checkPersistentLoginCookie( pwmRequest, storedConfig );
-            if ( persistentLoginPassed )
-            {
-                return processLoginSuccess( pwmRequest, persistentLoginEnabled );
-            }
-        }
-
-        final String password = pwmRequest.readParameterAsString( "password" );
-
-        boolean passwordAccepted = false;
-        if ( !StringUtil.isEmpty( password ) )
-        {
-            if ( storedConfig.verifyPassword( password, pwmRequest.getConfig() ) )
-            {
-                passwordAccepted = true;
-                LOGGER.trace( pwmRequest, () -> "valid configuration password accepted" );
-                updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), true );
-            }
-            else
-            {
-                LOGGER.trace( pwmRequest, () -> "configuration password is not correct" );
-                pwmApplication.getIntruderManager().convenience().markAddressAndSession( pwmSession );
-                pwmApplication.getIntruderManager().mark( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel() );
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_PASSWORD_ONLY_BAD );
-                updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), false );
-                return denyAndError( pwmRequest, errorInformation );
-            }
-        }
-
-        if ( passwordAccepted )
+        if ( !pwmRequest.getURL().isPwmServletURL( PwmServletDefinition.ConfigManager_Login ) )
         {
-            return processLoginSuccess( pwmRequest, persistentLoginEnabled );
-        }
-
-        configManagerBean.setPrePasswordEntryUrl( pwmRequest.getHttpServletRequest().getRequestURL().toString() );
-
-        forwardToJsp( pwmRequest );
-        return ProcessStatus.Halt;
-    }
-
-    private static void writePersistentLoginCookie( final PwmRequest pwmRequest )
-            throws PwmUnrecoverableException
-    {
-        final int persistentSeconds = figureMaxLoginSeconds( pwmRequest );
-
-        if ( persistentSeconds > 0 )
-        {
-            final TimeDuration persistenceDuration = TimeDuration.of( persistentSeconds, TimeDuration.Unit.SECONDS );
-            final Instant expirationDate = persistenceDuration.incrementFromInstant( Instant.now() );
-            final StoredConfigurationImpl storedConfig = pwmRequest.getConfig().getStoredConfiguration();
-            final String persistentLoginValue = makePersistentLoginPassword( pwmRequest, storedConfig );
-            final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo( expirationDate, persistentLoginValue );
-            final String cookieValue = pwmRequest.getPwmApplication().getSecureService().encryptObjectToString( persistentLoginInfo );
-            pwmRequest.getPwmResponse().writeCookie(
-                    COOKIE_NAME,
-                    cookieValue,
-                    persistentSeconds,
-                    COOKIE_PATH
-            );
-            LOGGER.debug( pwmRequest, () -> "set persistent config login cookie (expires "
-                    + JavaHelper.toIsoDate( expirationDate )
-                    + ")"
-            );
-        }
-    }
-
-    private static boolean checkPersistentLoginCookie(
-            final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfig
-
-    )
-    {
-        try
-        {
-            final String cookieValue = pwmRequest.readCookie( COOKIE_NAME );
-            if ( !StringUtil.isEmpty( cookieValue ) )
-            {
-                final PersistentLoginInfo persistentLoginInfo = pwmRequest.getPwmApplication().getSecureService().decryptObject( cookieValue, PersistentLoginInfo.class );
-                if ( persistentLoginInfo != null )
-                {
-                    if ( persistentLoginInfo.getExpireDate().isAfter( Instant.now() ) )
-                    {
-                        final String persistentLoginPassword = makePersistentLoginPassword( pwmRequest, storedConfig );
-                        if ( StringUtil.nullSafeEquals( persistentLoginPassword, persistentLoginInfo.getPassword() ) )
-                        {
-                            LOGGER.debug( pwmRequest, () -> "accepting persistent config login from cookie (expires "
-                                    + JavaHelper.toIsoDate( persistentLoginInfo.getExpireDate() )
-                                    + ")"
-                            );
-                            return true;
-                        }
-                    }
-
-                    pwmRequest.getPwmResponse().removeCookie( COOKIE_NAME, COOKIE_PATH );
-                    LOGGER.debug( pwmRequest, () -> "removing non-working persistent config login cookie" );
-                }
-            }
-        }
-        catch ( Exception e )
-        {
-            LOGGER.error( pwmRequest, "error examining persistent config login cookie: " + e.getMessage() );
+            configManagerBean.setPrePasswordEntryUrl( pwmRequest.getHttpServletRequest().getRequestURL().toString() );
+            pwmRequest.sendRedirect( PwmServletDefinition.ConfigManager_Login );
+            return ProcessStatus.Halt;
         }
-
-        return false;
+        return ProcessStatus.Continue;
     }
 
-
     private static void checkPreconditions(
             final PwmRequest pwmRequest,
             final StoredConfigurationImpl storedConfig
@@ -272,164 +144,6 @@ public class ConfigAccessFilter extends AbstractPwmFilter
             {
                 throw new PwmUnrecoverableException( PwmError.ERROR_UNAUTHORIZED );
             }
-         }
-    }
-
-    private static boolean persistentLoginEnabled(
-            final PwmRequest pwmRequest
-    )
-    {
-        if ( pwmRequest.getConfig().isDefaultValue( PwmSetting.PWM_SECURITY_KEY ) )
-        {
-            LOGGER.debug( pwmRequest, () -> "security not available, persistent login not possible." );
-            return false;
         }
-
-        return true;
-    }
-
-    private static String makePersistentLoginPassword(
-            final PwmRequest pwmRequest,
-            final StoredConfiguration storedConfig
-    )
-            throws PwmUnrecoverableException
-    {
-        final int hashChars = 32;
-        String hashValue = storedConfig.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
-
-        if ( PwmApplicationMode.RUNNING == pwmRequest.getPwmApplication().getApplicationMode() )
-        {
-            final PwmSession pwmSession = pwmRequest.getPwmSession();
-            hashValue += pwmSession.getUserInfo().getUserIdentity().toDelimitedKey();
-        }
-
-        return StringUtil.truncate( SecureEngine.hash( hashValue, PwmHashAlgorithm.SHA512 ), hashChars );
-    }
-
-    private static void forwardToJsp( final PwmRequest pwmRequest )
-            throws ServletException, PwmUnrecoverableException, IOException
-    {
-        final int persistentSeconds = figureMaxLoginSeconds( pwmRequest );
-        final String time = TimeDuration.of( persistentSeconds, TimeDuration.Unit.SECONDS ).asLongString( pwmRequest.getLocale() );
-
-        final ConfigLoginHistory configLoginHistory = readConfigLoginHistory( pwmRequest );
-
-        pwmRequest.setAttribute( PwmRequestAttribute.ConfigLoginHistory, configLoginHistory );
-        pwmRequest.setAttribute( PwmRequestAttribute.ConfigPasswordRememberTime, time );
-        pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_LOGIN );
-
-    }
-
-    private static ConfigLoginHistory readConfigLoginHistory( final PwmRequest pwmRequest )
-    {
-        final ConfigLoginHistory configLoginHistory = pwmRequest.getPwmApplication().readAppAttribute( PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class );
-        return configLoginHistory == null
-                ? new ConfigLoginHistory()
-                : configLoginHistory;
-    }
-
-    private static void updateLoginHistory( final PwmRequest pwmRequest, final UserIdentity userIdentity, final boolean successful )
-    {
-        final ConfigLoginHistory configLoginHistory = readConfigLoginHistory( pwmRequest );
-        final ConfigLoginEvent event = new ConfigLoginEvent(
-                userIdentity == null ? "n/a" : userIdentity.toDisplayString(),
-                Instant.now(),
-                pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress()
-        );
-        final int maxEvents = Integer.parseInt( pwmRequest.getPwmApplication().getConfig().readAppProperty( AppProperty.CONFIG_HISTORY_MAX_ITEMS ) );
-        configLoginHistory.addEvent( event, maxEvents, successful );
-        pwmRequest.getPwmApplication().writeAppAttribute( PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, configLoginHistory );
-    }
-
-    @Value
-    private static class PersistentLoginInfo implements Serializable
-    {
-        @SerializedName( "e" )
-        private Instant expireDate;
-
-        @SerializedName( "p" )
-        private String password;
-    }
-
-    @Value
-    public static class ConfigLoginHistory implements Serializable
-    {
-        private List<ConfigLoginEvent> successEvents = new ArrayList<>();
-        private List<ConfigLoginEvent> failedEvents = new ArrayList<>();
-
-        void addEvent( final ConfigLoginEvent event, final int maxEvents, final boolean successful )
-        {
-            final List<ConfigLoginEvent> events = successful ? successEvents : failedEvents;
-            events.add( event );
-            if ( maxEvents > 0 )
-            {
-                while ( events.size() > maxEvents )
-                {
-                    events.remove( 0 );
-                }
-            }
-        }
-
-        public List<ConfigLoginEvent> successEvents( )
-        {
-            return Collections.unmodifiableList( successEvents );
-        }
-
-        public List<ConfigLoginEvent> failedEvents( )
-        {
-            return Collections.unmodifiableList( failedEvents );
-        }
-    }
-
-    @Value
-    public static class ConfigLoginEvent implements Serializable
-    {
-        private final String userIdentity;
-        private final Instant date;
-        private final String networkAddress;
-    }
-
-    private static int figureMaxLoginSeconds( final PwmRequest pwmRequest )
-    {
-        return JavaHelper.silentParseInt(
-                pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS ),
-                (int) TimeDuration.HOUR.as( TimeDuration.Unit.SECONDS )
-        );
-    }
-
-
-    private static ProcessStatus denyAndError( final PwmRequest pwmRequest, final ErrorInformation errorInformation )
-            throws ServletException, PwmUnrecoverableException, IOException
-    {
-        pwmRequest.respondWithError( errorInformation );
-        return ProcessStatus.Halt;
-    }
-
-    private static ProcessStatus processLoginSuccess( final PwmRequest pwmRequest, final boolean persistentLoginEnabled )
-            throws PwmUnrecoverableException, IOException
-    {
-        final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
-
-        configManagerBean.setPasswordVerified( true );
-        pwmApplication.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
-        pwmApplication.getIntruderManager().clear( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
-        pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded( true );
-        if ( persistentLoginEnabled && "on".equals( pwmRequest.readParameterAsString( "remember" ) ) )
-        {
-            writePersistentLoginCookie( pwmRequest );
-        }
-
-        if ( configManagerBean.getPrePasswordEntryUrl() != null )
-        {
-            final String originalUrl = configManagerBean.getPrePasswordEntryUrl();
-            configManagerBean.setPrePasswordEntryUrl( null );
-            pwmRequest.getPwmResponse().sendRedirect( originalUrl );
-            return ProcessStatus.Halt;
-        }
-
-        pwmRequest.sendRedirect( pwmRequest.getURLwithQueryString() );
-        return ProcessStatus.Continue;
     }
 }

+ 125 - 99
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -67,6 +67,8 @@ import java.math.BigInteger;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -297,17 +299,15 @@ public class RequestInitializationFilter implements Filter
     }
 
     private void checkIfSessionRecycleNeeded( final PwmRequest pwmRequest )
-            throws IOException, ServletException
     {
         if ( pwmRequest.getPwmSession().getSessionStateBean().isSessionIdRecycleNeeded() )
         {
             pwmRequest.getHttpServletRequest().changeSessionId();
             pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded( false );
         }
-
     }
 
-    public static void addPwmResponseHeaders(
+    private static void addPwmResponseHeaders(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException
@@ -436,7 +436,7 @@ public class RequestInitializationFilter implements Filter
             return "";
         }
 
-        final String userIPAddress = readUserIPAddress( request, config );
+        final String userIPAddress = readUserNetworkAddress( request, config );
         try
         {
             return InetAddress.getByName( userIPAddress ).getCanonicalHostName();
@@ -455,51 +455,47 @@ public class RequestInitializationFilter implements Filter
      * @param request the http request object
      * @param config the application configuration
      * @return String containing the textual representation of the source IP address, or null if the request is invalid.
-     * @throws PwmUnrecoverableException if unable to read the network address
      */
-    public static String readUserIPAddress(
+    public static String readUserNetworkAddress(
             final HttpServletRequest request,
             final Configuration config
     )
-            throws PwmUnrecoverableException
     {
-        final boolean useXForwardedFor = config != null && config.readSettingAsBoolean( PwmSetting.USE_X_FORWARDED_FOR_HEADER );
-
-        String userIP = "";
+        final List<String> candidateAddresses = new ArrayList<>();
 
+        final boolean useXForwardedFor = config != null && config.readSettingAsBoolean( PwmSetting.USE_X_FORWARDED_FOR_HEADER );
         if ( useXForwardedFor )
         {
-            userIP = request.getHeader( HttpHeader.XForwardedFor.getHttpName() );
-            if ( !StringUtil.isEmpty( userIP ) )
+            final String xForwardedForValue = request.getHeader( HttpHeader.XForwardedFor.getHttpName() );
+            if ( !StringUtil.isEmpty( xForwardedForValue ) )
             {
-                final int commaIndex = userIP.indexOf( ',' );
-                if ( commaIndex > -1 )
-                {
-                    userIP = userIP.substring( 0, commaIndex );
-                }
+                Collections.addAll( candidateAddresses, xForwardedForValue.split( "," ) );
             }
+        }
 
-            if ( !StringUtil.isEmpty( userIP ) )
-            {
-                if ( !InetAddressValidator.getInstance().isValid( userIP ) )
-                {
-                    LOGGER.warn( "discarding bogus network address '" + userIP + "' in "
-                            + HttpHeader.XForwardedFor.getHttpName() + " header" );
-                    userIP = null;
-                }
-            }
+        final String sourceIP = request.getRemoteAddr();
+        if ( !StringUtil.isEmpty( sourceIP ) )
+        {
+            candidateAddresses.add( sourceIP );
         }
 
-        if ( StringUtil.isEmpty( userIP ) )
+        for ( final String candidateAddress : candidateAddresses )
         {
-            userIP = request.getRemoteAddr();
+            final String trimAddr = candidateAddress.trim();
+            if ( InetAddressValidator.getInstance().isValid( trimAddr ) )
+            {
+                return trimAddr;
+            }
+            else
+            {
+                LOGGER.warn( "discarding bogus source network address '" + trimAddr + "'" );
+            }
         }
 
-        return userIP == null ? "" : userIP;
+        return "";
     }
 
-
-    public static void handleRequestInitialization(
+    private static void handleRequestInitialization(
             final PwmRequest pwmRequest
     )
             throws PwmUnrecoverableException
@@ -517,7 +513,7 @@ public class RequestInitializationFilter implements Filter
         // mark session ip address
         if ( ssBean.getSrcAddress() == null )
         {
-            ssBean.setSrcAddress( readUserIPAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getConfig() ) );
+            ssBean.setSrcAddress( readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getConfig() ) );
         }
 
         // mark the user's hostname in the session bean
@@ -574,18 +570,39 @@ public class RequestInitializationFilter implements Filter
         }
     }
 
-    @SuppressWarnings( "checkstyle:MethodLength" )
-    public static void handleRequestSecurityChecks(
-            final PwmRequest pwmRequest
-    )
+    private static void handleRequestSecurityChecks( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException
     {
-        final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
-
         // check the user's IP address
+        checkIfSourceAddressChanged( pwmRequest );
+
+        // check total time.
+        checkTotalSessionTime( pwmRequest );
+
+        // check headers
+        checkRequiredHeaders( pwmRequest );
+
+        // check permitted source IP address
+        checkSourceNetworkAddress( pwmRequest );
+
+        // csrf cross-site request forgery checks
+        checkCsrfHeader( pwmRequest );
+
+        // check trial
+        checkTrial( pwmRequest );
+
+        // check intruder
+        pwmRequest.getPwmApplication().getIntruderManager().convenience().checkAddressAndSession( pwmRequest.getPwmSession() );
+    }
+
+    private static void checkIfSourceAddressChanged( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
         if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.MULTI_IP_SESSION_ALLOWED ) )
         {
-            final String remoteAddress = readUserIPAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getConfig() );
+            final String remoteAddress = readUserNetworkAddress( pwmRequest.getHttpServletRequest(), pwmRequest.getConfig() );
+            final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
+
             if ( !ssBean.getSrcAddress().equals( remoteAddress ) )
             {
                 final String errorMsg = "current network address '" + remoteAddress + "' has changed from original network address '" + ssBean.getSrcAddress() + "'";
@@ -593,100 +610,109 @@ public class RequestInitializationFilter implements Filter
                 throw new PwmUnrecoverableException( errorInformation );
             }
         }
+    }
 
-        // check total time.
+    private static void checkTotalSessionTime( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final LocalSessionStateBean ssBean = pwmRequest.getPwmSession().getSessionStateBean();
+
+        if ( ssBean.getSessionCreationTime() != null )
         {
-            if ( ssBean.getSessionCreationTime() != null )
+            final long maxSessionSeconds = pwmRequest.getConfig().readSettingAsLong( PwmSetting.SESSION_MAX_SECONDS );
+            final TimeDuration sessionAge = TimeDuration.fromCurrent( ssBean.getSessionCreationTime() );
+            final int sessionSecondAge = (int) sessionAge.as( TimeDuration.Unit.SECONDS );
+            if ( sessionSecondAge > maxSessionSeconds )
             {
-                final Long maxSessionSeconds = pwmRequest.getConfig().readSettingAsLong( PwmSetting.SESSION_MAX_SECONDS );
-                final TimeDuration sessionAge = TimeDuration.fromCurrent( ssBean.getSessionCreationTime() );
-                final int sessionSecondAge = (int) sessionAge.as( TimeDuration.Unit.SECONDS );
-                if ( sessionSecondAge > maxSessionSeconds )
-                {
-                    final String errorMsg = "session age (" + sessionAge.asCompactString() + ") is longer than maximum permitted age";
-                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
-                    throw new PwmUnrecoverableException( errorInformation );
-                }
+                final String errorMsg = "session age (" + sessionAge.asCompactString() + ") is longer than maximum permitted age";
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
+                throw new PwmUnrecoverableException( errorInformation );
             }
         }
+    }
 
-        // check headers
+    private static void checkRequiredHeaders( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final List<String> requiredHeaders = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.REQUIRED_HEADERS );
+        if ( requiredHeaders != null && !requiredHeaders.isEmpty() )
         {
-            final List<String> requiredHeaders = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.REQUIRED_HEADERS );
-            if ( requiredHeaders != null && !requiredHeaders.isEmpty() )
-            {
-                final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair( requiredHeaders, "=" );
+            final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair( requiredHeaders, "=" );
 
-                for ( final Map.Entry<String, String> entry : configuredValues.entrySet() )
+            for ( final Map.Entry<String, String> entry : configuredValues.entrySet() )
+            {
+                final String key = entry.getKey();
+                if ( key != null && key.length() > 0 )
                 {
-                    final String key = entry.getKey();
-                    if ( key != null && key.length() > 0 )
+                    final String requiredValue = entry.getValue();
+                    if ( requiredValue != null && requiredValue.length() > 0 )
                     {
-                        final String requiredValue = entry.getValue();
-                        if ( requiredValue != null && requiredValue.length() > 0 )
+                        final String value = pwmRequest.readHeaderValueAsString( key );
+                        if ( value == null || value.length() < 1 )
+                        {
+                            final String errorMsg = "request is missing required value for header '" + key + "'";
+                            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
+                            throw new PwmUnrecoverableException( errorInformation );
+                        }
+                        else
                         {
-                            final String value = pwmRequest.readHeaderValueAsString( key );
-                            if ( value == null || value.length() < 1 )
+                            if ( !requiredValue.equals( value ) )
                             {
-                                final String errorMsg = "request is missing required value for header '" + key + "'";
+                                final String errorMsg = "request has incorrect required value for header '" + key + "'";
                                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
                                 throw new PwmUnrecoverableException( errorInformation );
                             }
-                            else
-                            {
-                                if ( !requiredValue.equals( value ) )
-                                {
-                                    final String errorMsg = "request has incorrect required value for header '" + key + "'";
-                                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
-                                    throw new PwmUnrecoverableException( errorInformation );
-                                }
-                            }
                         }
                     }
                 }
             }
         }
+    }
 
-        // check permitted source IP address
+    private static void checkSourceNetworkAddress( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final List<String> requiredHeaders = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.IP_PERMITTED_RANGE );
+        if ( requiredHeaders != null && !requiredHeaders.isEmpty() )
         {
-            final List<String> requiredHeaders = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.IP_PERMITTED_RANGE );
-            if ( requiredHeaders != null && !requiredHeaders.isEmpty() )
+            boolean match = false;
+            final String requestAddress = pwmRequest.getHttpServletRequest().getRemoteAddr();
+            for ( int i = 0; i < requiredHeaders.size() && !match; i++ )
             {
-                boolean match = false;
-                final String requestAddress = pwmRequest.getHttpServletRequest().getRemoteAddr();
-                for ( int i = 0; i < requiredHeaders.size() && !match; i++ )
+                final String ipMatchString = requiredHeaders.get( i );
+                try
                 {
-                    final String ipMatchString = requiredHeaders.get( i );
+                    final IPMatcher ipMatcher = new IPMatcher( ipMatchString );
                     try
                     {
-                        final IPMatcher ipMatcher = new IPMatcher( ipMatchString );
-                        try
-                        {
-                            if ( ipMatcher.match( requestAddress ) )
-                            {
-                                match = true;
-                            }
-                        }
-                        catch ( IPMatcher.IPMatcherException e )
+                        if ( ipMatcher.match( requestAddress ) )
                         {
-                            LOGGER.error( "error while attempting to match permitted address range '" + ipMatchString + "', error: " + e );
+                            match = true;
                         }
                     }
                     catch ( IPMatcher.IPMatcherException e )
                     {
-                        LOGGER.error( "error parsing permitted address range '" + ipMatchString + "', error: " + e );
+                        LOGGER.error( "error while attempting to match permitted address range '" + ipMatchString + "', error: " + e );
                     }
                 }
-                if ( !match )
+                catch ( IPMatcher.IPMatcherException e )
                 {
-                    final String errorMsg = "request network address '" + requestAddress + "' does not match any configured permitted source address";
-                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
-                    throw new PwmUnrecoverableException( errorInformation );
+                    LOGGER.error( "error parsing permitted address range '" + ipMatchString + "', error: " + e );
                 }
             }
+            if ( !match )
+            {
+                final String errorMsg = "request network address '" + requestAddress + "' does not match any configured permitted source address";
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SECURITY_VIOLATION, errorMsg );
+                throw new PwmUnrecoverableException( errorInformation );
+            }
         }
+    }
 
-        //  csrf cross-site request forgery checks
+
+    private static void checkCsrfHeader( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
         final boolean performCsrfHeaderChecks = Boolean.parseBoolean( pwmRequest.getConfig().readAppProperty( AppProperty.SECURITY_HTTP_PERFORM_CSRF_HEADER_CHECKS ) );
         if (
                 performCsrfHeaderChecks
@@ -751,8 +777,11 @@ public class RequestInitializationFilter implements Filter
                 throw new PwmUnrecoverableException( errorInformation );
             }
         }
+    }
 
-        // check trial
+    private static void checkTrial ( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
         if ( PwmConstants.TRIAL_MODE )
         {
             final StatisticsManager statisticsManager = pwmRequest.getPwmApplication().getStatisticsManager();
@@ -768,9 +797,6 @@ public class RequestInitializationFilter implements Filter
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_TRIAL_VIOLATION, "maximum usage for this server has been exceeded" ) );
             }
         }
-
-        // check intruder
-        pwmRequest.getPwmApplication().getIntruderManager().convenience().checkAddressAndSession( pwmRequest.getPwmSession() );
     }
 
     private void checkIdleTimeout( final PwmRequest pwmRequest ) throws PwmUnrecoverableException

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

@@ -48,7 +48,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( AbstractPwmServlet.class );
 
-    private Map<String, Method> actionMethodCache;
+    private final Map<String, Method> actionMethodCache = createMethodCache();
 
     public String servletUriRemainder( final PwmRequest pwmRequest, final String command ) throws PwmUnrecoverableException
     {
@@ -112,7 +112,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         }
         try
         {
-            final Method interestedMethod = discoverMethodForAction( this.getClass(), action );
+            final Method interestedMethod = actionMethodCache.get( action.toString() );
             if ( interestedMethod != null )
             {
                 interestedMethod.setAccessible( true );
@@ -211,25 +211,20 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         String action( );
     }
 
-    private Method discoverMethodForAction( final Class clazz, final ProcessAction action )
+    private Map<String, Method> createMethodCache()
     {
-        if ( actionMethodCache == null )
+        final Map<String, Method> map = new HashMap<>();
+        final Collection<Method> methods = JavaHelper.getAllMethodsForClass( this.getClass() );
+        for ( Method method : methods )
         {
-            final Map<String, Method> map = new HashMap<>();
-            final Collection<Method> methods = JavaHelper.getAllMethodsForClass( clazz );
-            for ( Method method : methods )
+            if ( method.getAnnotation( ActionHandler.class ) != null )
             {
-                if ( method.getAnnotation( ActionHandler.class ) != null )
-                {
-                    final String actionName = method.getAnnotation( ActionHandler.class ).action();
-                    map.put( actionName, method );
+                final String actionName = method.getAnnotation( ActionHandler.class ).action();
+                map.put( actionName, method );
 
-                }
             }
-            actionMethodCache = Collections.unmodifiableMap( map );
         }
-
-        return actionMethodCache.get( action.toString() );
+        return Collections.unmodifiableMap( map );
     }
 }
 

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

@@ -30,7 +30,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.DeleteAccountProfile;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -181,7 +181,7 @@ public class DeleteAccountServlet extends ControlledPwmServlet
                     AuditEvent.AGREEMENT_PASSED,
                     pwmRequest.getUserInfoIfLoggedIn(),
                     pwmRequest.getSessionLabel(),
-                    ProfileType.DeleteAccount.toString()
+                    ProfileDefinition.DeleteAccount.toString()
             );
             pwmRequest.getPwmApplication().getAuditManager().submit( auditRecord );
         }

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

@@ -48,6 +48,7 @@ import password.pwm.http.servlet.configeditor.ConfigEditorServlet;
 import password.pwm.http.servlet.configguide.ConfigGuideServlet;
 import password.pwm.http.servlet.configmanager.ConfigManagerCertificatesServlet;
 import password.pwm.http.servlet.configmanager.ConfigManagerLocalDBServlet;
+import password.pwm.http.servlet.configmanager.ConfigManagerLoginServlet;
 import password.pwm.http.servlet.configmanager.ConfigManagerServlet;
 import password.pwm.http.servlet.configmanager.ConfigManagerWordlistServlet;
 import password.pwm.http.servlet.newuser.NewUserServlet;
@@ -90,6 +91,7 @@ public enum PwmServletDefinition
     ConfigGuide( ConfigGuideServlet.class, ConfigGuideBean.class ),
     ConfigEditor( ConfigEditorServlet.class, null ),
     ConfigManager( ConfigManagerServlet.class, ConfigManagerBean.class ),
+    ConfigManager_Login( ConfigManagerLoginServlet.class, ConfigManagerBean.class ),
     ConfigManager_Wordlists( ConfigManagerWordlistServlet.class, ConfigManagerBean.class ),
     ConfigManager_LocalDB( ConfigManagerLocalDBServlet.class, ConfigManagerBean.class ),
     ConfigManager_Certificates( ConfigManagerCertificatesServlet.class, ConfigManagerBean.class ),

+ 28 - 26
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java

@@ -30,8 +30,8 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.profile.ActivateUserProfile;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -47,7 +47,6 @@ import password.pwm.http.bean.ActivateUserBean;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
@@ -97,7 +96,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
 
     public enum ActivateUserAction implements ProcessAction
     {
-        activate( HttpMethod.POST ),
+        search( HttpMethod.POST ),
         tokenChoice( HttpMethod.POST ),
         enterCode( HttpMethod.POST, HttpMethod.GET ),
         reset( HttpMethod.POST ),
@@ -159,7 +158,8 @@ public class ActivateUserServlet extends ControlledPwmServlet
         return ProcessStatus.Continue;
     }
 
-    static UserInfo userInfo ( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    static UserInfo userInfo ( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
         final ActivateUserBean activateUserBean = activateUserBean( pwmRequest );
         return UserInfoFactory.newUserInfoUsingProxy(
@@ -175,6 +175,19 @@ public class ActivateUserServlet extends ControlledPwmServlet
         return pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
     }
 
+    static ActivateUserProfile activateUserProfile( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final ActivateUserBean activateUserBean = activateUserBean( pwmRequest );
+        final String profileID = activateUserBean.getProfileID();
+        final ActivateUserProfile activateUserProfile = pwmRequest.getConfig().getUserActivationProfiles().get( profileID );
+        if ( activateUserProfile == null )
+        {
+            throw  PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "unable to load activate user profile" );
+        }
+        return activateUserProfile;
+    }
+
     @ActionHandler( action = "reset" )
     public ProcessStatus handleResetRequest( final PwmRequest pwmRequest )
             throws ServletException, PwmUnrecoverableException, IOException
@@ -185,7 +198,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
         {
             case exitActivation:
                 pwmRequest.getPwmApplication().getSessionStateService().clearBean( pwmRequest, ActivateUserBean.class );
-                ActivateUserUtils.forwardToActivateUserForm( pwmRequest );
+                ActivateUserUtils.forwardToSearchUserForm( pwmRequest );
                 return ProcessStatus.Halt;
 
             case clearTokenDestination:
@@ -200,8 +213,8 @@ public class ActivateUserServlet extends ControlledPwmServlet
         return ProcessStatus.Continue;
     }
 
-    @ActionHandler( action = "activate" )
-    public ProcessStatus handleActivateRequest( final PwmRequest pwmRequest )
+    @ActionHandler( action = "search" )
+    public ProcessStatus handleSearchRequest( final PwmRequest pwmRequest )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
@@ -258,19 +271,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
 
             ActivateUserUtils.validateParamsAgainstLDAP( pwmRequest, formValues, userIdentity );
 
-            final List<UserPermission> userPermissions = config.readSettingAsUserPermission( PwmSetting.ACTIVATE_USER_QUERY_MATCH );
-            if ( !LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession.getLabel(), userIdentity, userPermissions ) )
-            {
-                final String errorMsg = "user " + userIdentity + " attempted activation, but does not match query string";
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_ACTIVATE_NO_PERMISSION, errorMsg );
-                pwmApplication.getIntruderManager().convenience().markUserIdentity( userIdentity, pwmSession );
-                pwmApplication.getIntruderManager().convenience().markAddressAndSession( pwmSession );
-                throw new PwmUnrecoverableException( errorInformation );
-            }
-
-            final ActivateUserBean activateUserBean = pwmApplication.getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
-            activateUserBean.setUserIdentity( userIdentity );
-            activateUserBean.setFormValidated( true );
+            ActivateUserUtils.initUserActivationBean( pwmRequest, userIdentity );
             pwmApplication.getIntruderManager().convenience().clearAttributes( formValues );
             pwmApplication.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
         }
@@ -290,7 +291,8 @@ public class ActivateUserServlet extends ControlledPwmServlet
             throws PwmUnrecoverableException
     {
         final UserInfo userInfo = userInfo( pwmRequest );
-        final MessageSendMethod tokenSendMethod = pwmRequest.getConfig().readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
+        final ActivateUserProfile activateUserProfile = activateUserProfile( pwmRequest );
+        final MessageSendMethod tokenSendMethod = activateUserProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
 
         final List<TokenDestinationItem> tokenDestinationItems = TokenUtil.figureAvailableTokenDestinations(
                 pwmRequest.getPwmApplication(),
@@ -399,19 +401,19 @@ public class ActivateUserServlet extends ControlledPwmServlet
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final Configuration config = pwmApplication.getConfig();
         final ActivateUserBean activateUserBean = activateUserBean( pwmRequest );
 
         if ( !activateUserBean.isFormValidated() || activateUserBean.getUserIdentity() == null )
         {
-            ActivateUserUtils.forwardToActivateUserForm( pwmRequest );
+            ActivateUserUtils.forwardToSearchUserForm( pwmRequest );
             return;
         }
 
         final UserInfo userInfo = userInfo( pwmRequest );
+        final ActivateUserProfile activateUserProfile = activateUserProfile( pwmRequest );
 
-        final MessageSendMethod tokenSendMethod = config.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
-        if ( MessageSendMethod.NONE != tokenSendMethod )
+        final MessageSendMethod tokenSendMethod = activateUserProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
+        if ( tokenSendMethod != MessageSendMethod.NONE && tokenSendMethod != null )
         {
             final List<TokenDestinationItem> tokenDestinationItems = TokenUtil.figureAvailableTokenDestinations(
                     pwmApplication,
@@ -457,7 +459,7 @@ public class ActivateUserServlet extends ControlledPwmServlet
             }
         }
 
-        final String agreementText = config.readSettingAsLocalizedString(
+        final String agreementText = activateUserProfile.readSettingAsLocalizedString(
                 PwmSetting.ACTIVATE_AGREEMENT_MESSAGE,
                 pwmSession.getSessionStateBean().getLocale()
         );

+ 36 - 66
server/src/main/java/password/pwm/http/servlet/activation/ActivateUserUtils.java

@@ -25,7 +25,6 @@ import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException;
 import com.novell.ldapchai.provider.ChaiProvider;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import password.pwm.PwmApplication;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.LoginInfoBean;
@@ -33,7 +32,10 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.MessageSendMethod;
+import password.pwm.config.profile.ActivateUserProfile;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.profile.ProfileDefinition;
+import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
@@ -45,14 +47,15 @@ import password.pwm.http.JspUrl;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
+import password.pwm.http.bean.ActivateUserBean;
 import password.pwm.ldap.UserInfo;
 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.stats.Statistic;
-import password.pwm.util.PostChangePasswordAction;
 import password.pwm.util.form.FormUtility;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.ActionExecutor;
@@ -62,6 +65,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 
 class ActivateUserUtils
 {
@@ -71,8 +75,6 @@ class ActivateUserUtils
     {
     }
 
-
-    @SuppressFBWarnings( "SE_BAD_FIELD" )
     static void activateUser(
             final PwmRequest pwmRequest,
             final UserIdentity userIdentity
@@ -81,9 +83,11 @@ class ActivateUserUtils
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final Configuration config = pwmApplication.getConfig();
         final ChaiUser theUser = pwmApplication.getProxiedChaiUser( userIdentity );
-        if ( config.readSettingAsBoolean( PwmSetting.ACTIVATE_USER_UNLOCK ) )
+
+        final ActivateUserProfile activateUserProfile = ActivateUserServlet.activateUserProfile( pwmRequest );
+
+        if ( activateUserProfile.readSettingAsBoolean( PwmSetting.ACTIVATE_USER_UNLOCK ) )
         {
             try
             {
@@ -102,8 +106,8 @@ class ActivateUserUtils
             {
                 // execute configured actions
                 LOGGER.debug( pwmSession.getLabel(), () -> "executing configured pre-actions to user " + theUser.getEntryDN() );
-                final List<ActionConfiguration> configValues = config.readSettingAsAction( PwmSetting.ACTIVATE_USER_PRE_WRITE_ATTRIBUTES );
-                if ( configValues != null && !configValues.isEmpty() )
+                final List<ActionConfiguration> configValues = activateUserProfile.readSettingAsAction( PwmSetting.ACTIVATE_USER_PRE_WRITE_ATTRIBUTES );
+                if ( !JavaHelper.isEmpty( configValues ) )
                 {
                     final MacroMachine macroMachine = MacroMachine.forUser( pwmRequest, userIdentity );
 
@@ -134,58 +138,6 @@ class ActivateUserUtils
 
             // send email or sms
             sendPostActivationNotice( pwmRequest );
-
-            // setup post-change attributes
-            final PostChangePasswordAction postAction = new PostChangePasswordAction()
-            {
-
-                public String getLabel( )
-                {
-                    return "ActivateUser write attributes";
-                }
-
-                public boolean doAction( final PwmSession pwmSession, final String newPassword )
-                        throws PwmUnrecoverableException
-                {
-                    try
-                    {
-                        {
-                            // execute configured actions
-                            LOGGER.debug( pwmSession.getLabel(), () -> "executing post-activate configured actions to user " + userIdentity.toDisplayString() );
-
-                            final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine( pwmApplication );
-                            final List<ActionConfiguration> configValues = pwmApplication.getConfig().readSettingAsAction( PwmSetting.ACTIVATE_USER_POST_WRITE_ATTRIBUTES );
-
-                            final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmApplication, userIdentity )
-                                    .setExpandPwmMacros( true )
-                                    .setMacroMachine( macroMachine )
-                                    .createActionExecutor();
-                            actionExecutor.executeActions( configValues, pwmRequest.getSessionLabel() );
-                        }
-                    }
-                    catch ( PwmOperationalException e )
-                    {
-                        final ErrorInformation info = new ErrorInformation(
-                                PwmError.ERROR_ACTIVATION_FAILURE,
-                                e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues()
-                        );
-                        final PwmUnrecoverableException newException = new PwmUnrecoverableException( info );
-                        newException.initCause( e );
-                        throw newException;
-                    }
-                    catch ( ChaiUnavailableException e )
-                    {
-                        final String errorMsg = "unable to reach ldap server while writing post-activate attributes: " + e.getMessage();
-                        final ErrorInformation info = new ErrorInformation( PwmError.ERROR_ACTIVATION_FAILURE, errorMsg );
-                        final PwmUnrecoverableException newException = new PwmUnrecoverableException( info );
-                        newException.initCause( e );
-                        throw newException;
-                    }
-                    return true;
-                }
-            };
-
-            pwmSession.getUserSessionDataCacheBean().addPostChangePasswordActions( "activateUserWriteAttributes", postAction );
         }
         catch ( ImpossiblePasswordPolicyException e )
         {
@@ -255,11 +207,10 @@ class ActivateUserUtils
     )
             throws PwmUnrecoverableException, ChaiUnavailableException
     {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        final Configuration config = pwmApplication.getConfig();
+        final ActivateUserProfile activateUserProfile = ActivateUserServlet.activateUserProfile( pwmRequest );
         final UserInfo userInfo = pwmSession.getUserInfo();
-        final MessageSendMethod pref = MessageSendMethod.valueOf( config.readSettingAsString( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD ) );
+        final MessageSendMethod pref = activateUserProfile.readSettingAsEnum( PwmSetting.ACTIVATE_TOKEN_SEND_METHOD, MessageSendMethod.class );
 
         final boolean success;
         switch ( pref )
@@ -368,7 +319,8 @@ class ActivateUserUtils
     static void forwardToAgreementPage( final PwmRequest pwmRequest )
             throws ServletException, PwmUnrecoverableException, IOException
     {
-        final String agreementText = pwmRequest.getConfig().readSettingAsLocalizedString(
+        final ActivateUserProfile activateUserProfile = ActivateUserServlet.activateUserProfile( pwmRequest );
+        final String agreementText = activateUserProfile.readSettingAsLocalizedString(
                 PwmSetting.ACTIVATE_AGREEMENT_MESSAGE,
                 pwmRequest.getLocale()
         );
@@ -379,10 +331,28 @@ class ActivateUserUtils
         pwmRequest.forwardToJsp( JspUrl.ACTIVATE_USER_AGREEMENT );
     }
 
-    static void forwardToActivateUserForm( final PwmRequest pwmRequest )
+    static void forwardToSearchUserForm( final PwmRequest pwmRequest )
             throws ServletException, PwmUnrecoverableException, IOException
     {
         pwmRequest.addFormInfoToRequestAttr( PwmSetting.ACTIVATE_USER_FORM, false, false );
-        pwmRequest.forwardToJsp( JspUrl.ACTIVATE_USER );
+        pwmRequest.forwardToJsp( JspUrl.ACTIVATE_USER_SEARCH );
+    }
+
+    static void initUserActivationBean( final PwmRequest pwmRequest, final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final ActivateUserBean activateUserBean = pwmApplication.getSessionStateService().getBean( pwmRequest, ActivateUserBean.class );
+
+        final Optional<String> profileID = ProfileUtility.discoverProfileIDforUser( pwmRequest.commonValues(), userIdentity, ProfileDefinition.ActivateUser );
+
+        if ( !profileID.isPresent() || !pwmApplication.getConfig().getUserActivationProfiles().containsKey( profileID.get() ) )
+        {
+            throw PwmUnrecoverableException.newException( PwmError.ERROR_ACTIVATE_NO_PERMISSION, "no matching user activation profile for user" );
+        }
+
+        activateUserBean.setUserIdentity( userIdentity );
+        activateUserBean.setFormValidated( true );
+        activateUserBean.setProfileID( profileID.get() );
     }
 }

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

@@ -123,6 +123,7 @@ public class AppDashboardData implements Serializable
             final Locale locale,
             final Flag... flags
     )
+            throws PwmUnrecoverableException
     {
         final Instant startTime = Instant.now();
 
@@ -275,6 +276,7 @@ public class AppDashboardData implements Serializable
     }
 
     private static List<DisplayElement> makeLocalDbInfo( final PwmApplication pwmApplication, final Locale locale )
+            throws PwmUnrecoverableException
     {
         final List<DisplayElement> localDbInfo = new ArrayList<>();
         final String notApplicable = Display.getLocalizedMessage( locale, Display.Value_NotApplicable, pwmApplication.getConfig() );
@@ -284,7 +286,7 @@ public class AppDashboardData implements Serializable
                 "worlistSize",
                 DisplayElement.Type.number,
                 "Word List Dictionary Size",
-                numberFormat.format( pwmApplication.getWordlistManager().size() )
+                numberFormat.format( pwmApplication.getWordlistService().size() )
         ) );
 
         localDbInfo.add( new DisplayElement(

+ 2 - 2
server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java

@@ -24,7 +24,7 @@ import lombok.Builder;
 import lombok.Value;
 import password.pwm.Permission;
 import password.pwm.bean.pub.PublicUserInfoBean;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.ldap.UserInfo;
 import password.pwm.svc.pwnotify.PwNotifyUserStatus;
@@ -45,7 +45,7 @@ public class UserDebugDataBean implements Serializable
 
     private final PwmPasswordPolicy ldapPasswordPolicy;
     private final PwmPasswordPolicy configuredPasswordPolicy;
-    private final Map<ProfileType, String> profiles;
+    private final Map<ProfileDefinition, String> profiles;
 
     private final PwNotifyUserStatus pwNotifyUserStatus;
 }

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

@@ -27,7 +27,7 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.value.data.UserPermission;
@@ -67,7 +67,7 @@ public class UserDebugDataReader
 
         final Map<Permission, String> permissions = UserDebugDataReader.permissionMap( pwmApplication, sessionLabel, userIdentity );
 
-        final Map<ProfileType, String> profiles = UserDebugDataReader.profileMap( pwmApplication, sessionLabel, userIdentity );
+        final Map<ProfileDefinition, String> profiles = UserDebugDataReader.profileMap( pwmApplication, sessionLabel, userIdentity );
 
         final PwmPasswordPolicy ldapPasswordPolicy = PasswordUtility.readLdapPasswordPolicy( pwmApplication, pwmApplication.getProxiedChaiUser( userIdentity ) );
 
@@ -134,22 +134,26 @@ public class UserDebugDataReader
         return Collections.unmodifiableMap( results );
     }
 
-    private static Map<ProfileType, String> profileMap(
+    private static Map<ProfileDefinition, String> profileMap(
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity
     ) throws PwmUnrecoverableException
     {
-        final Map<ProfileType, String> results = new TreeMap<>();
-        for ( final ProfileType profileType : ProfileType.values() )
+        final Map<ProfileDefinition, String> results = new TreeMap<>();
+        for ( final ProfileDefinition profileDefinition : ProfileDefinition.values() )
         {
-            final String id = ProfileUtility.discoverProfileIDforUser(
-                    pwmApplication,
-                    sessionLabel,
-                    userIdentity,
-                    profileType
-            );
-            results.put( profileType, id );
+            if ( profileDefinition.isAuthenticated() )
+            {
+                final String id = ProfileUtility.discoverProfileIDforUser(
+                        pwmApplication,
+                        sessionLabel,
+                        userIdentity,
+                        profileDefinition
+                );
+
+                results.put( profileDefinition, id );
+            }
         }
         return Collections.unmodifiableMap( results );
     }

+ 0 - 1
server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java

@@ -83,7 +83,6 @@ import java.util.Map;
 
 public abstract class ChangePasswordServlet extends ControlledPwmServlet
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( ChangePasswordServlet.class );
 
     public enum ChangePasswordAction implements ControlledPwmServlet.ProcessAction

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

@@ -215,7 +215,7 @@ public class ChangePasswordServletUtil
         final ChangePasswordBean cpb = pwmApplication.getSessionStateService().getBean( pwmRequest, ChangePasswordBean.class );
 
         // change password
-        PasswordUtility.setActorPassword( pwmSession, pwmApplication, newPassword );
+        PasswordUtility.setActorPassword( pwmRequest, pwmApplication, newPassword );
 
         //init values for progress screen
         {

+ 5 - 2
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java

@@ -72,12 +72,12 @@ import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.ldap.LdapBrowser;
 import password.pwm.util.PasswordData;
-import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
+import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.queue.SmsQueueManager;
 import password.pwm.util.secure.HttpsServerCertificateManager;
 import password.pwm.ws.server.RestResultBean;
@@ -164,8 +164,11 @@ public class ConfigEditorServlet extends ControlledPwmServlet
     }
 
     @Override
-    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
+    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException, IOException, ServletException
     {
+        ConfigManagerServlet.verifyConfigAccess( pwmRequest );
+
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
 
         if ( configManagerBean.getStoredConfiguration() == null )

+ 2 - 0
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java

@@ -98,6 +98,8 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
     protected void processAction( final PwmRequest pwmRequest )
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
     {
+        ConfigManagerServlet.verifyConfigAccess( pwmRequest );
+
         final ConfigManagerCertificateAction action = readProcessAction( pwmRequest );
         final ArrayList<CertificateDebugDataItem> certificateDebugDataItems = new ArrayList<>( makeCertificateDebugData( pwmRequest.getConfig() ) );
 

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java

@@ -20,7 +20,6 @@
 
 package password.pwm.http.servlet.configmanager;
 
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -103,8 +102,9 @@ public class ConfigManagerLocalDBServlet extends AbstractPwmServlet
     }
 
     protected void processAction( final PwmRequest pwmRequest )
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+            throws ServletException, IOException, PwmUnrecoverableException
     {
+        ConfigManagerServlet.verifyConfigAccess( pwmRequest );
 
         final ConfigManagerAction processAction = readProcessAction( pwmRequest );
         if ( processAction != null )
@@ -143,7 +143,7 @@ public class ConfigManagerLocalDBServlet extends AbstractPwmServlet
         {
             final int bufferSize = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_DOWNLOAD_BUFFER_SIZE ) );
             final OutputStream bos = new BufferedOutputStream( resp.getOutputStream(), bufferSize );
-            localDBUtility.exportLocalDB( bos, LOGGER.asAppendable( PwmLogLevel.DEBUG, pwmRequest.getSessionLabel() ), true );
+            localDBUtility.exportLocalDB( bos, LOGGER.asAppendable( PwmLogLevel.DEBUG, pwmRequest.getSessionLabel() ) );
             LOGGER.debug( pwmRequest, () -> "completed localDBExport process in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
         }
         catch ( Exception e )

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

@@ -0,0 +1,420 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.http.servlet.configmanager;
+
+import com.google.gson.annotations.SerializedName;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import lombok.Value;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmApplicationMode;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.ConfigurationProperty;
+import password.pwm.config.stored.ConfigurationReader;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.ContextManager;
+import password.pwm.http.HttpMethod;
+import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
+import password.pwm.http.PwmHttpResponseWrapper;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
+import password.pwm.http.PwmSession;
+import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.http.servlet.AbstractPwmServlet;
+import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.svc.intruder.RecordType;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.secure.PwmHashAlgorithm;
+import password.pwm.util.secure.SecureEngine;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import java.io.IOException;
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+@WebServlet(
+        name = "ConfigManagerLogin",
+        urlPatterns = {
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/login",
+        }
+)
+public class ConfigManagerLoginServlet extends AbstractPwmServlet
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( ConfigManagerLoginServlet.class );
+
+    private static final String COOKIE_NAME = PwmConstants.COOKIE_PERSISTENT_CONFIG_LOGIN;
+    private static final PwmHttpResponseWrapper.CookiePath COOKIE_PATH = PwmHttpResponseWrapper.CookiePath.CurrentURL;
+
+    public enum ConfigManagerLoginAction implements ProcessAction
+    {
+        login( HttpMethod.POST ),;
+
+        private final HttpMethod method;
+
+        ConfigManagerLoginAction( final HttpMethod method )
+        {
+            this.method = method;
+        }
+
+        public Collection<HttpMethod> permittedMethods( )
+        {
+            return Collections.singletonList( method );
+        }
+    }
+
+    @Override
+    protected void processAction( final PwmRequest pwmRequest )
+            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+    {
+        checkPersistentLoginCookie( pwmRequest );
+
+        final ConfigManagerLoginAction processAction = readProcessAction( pwmRequest );
+        if ( processAction != null )
+        {
+            switch ( processAction )
+            {
+                case login:
+                    processLoginRequest( pwmRequest );
+                    break;
+
+                default:
+                    JavaHelper.unhandledSwitchStatement( processAction );
+
+            }
+            return;
+        }
+
+
+        final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
+        if ( configManagerBean.isPasswordVerified() )
+        {
+            forwardToNextUrl( pwmRequest );
+            return;
+
+        }
+        forwardToJsp( pwmRequest );
+    }
+
+    protected void processLoginRequest( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException, IOException, ServletException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration();
+
+        final String password = pwmRequest.readParameterAsString( "password" );
+        if ( !StringUtil.isEmpty( password ) )
+        {
+            if ( storedConfig.verifyPassword( password, pwmRequest.getConfig() ) )
+            {
+                LOGGER.trace( pwmRequest, () -> "valid configuration password accepted" );
+                updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), true );
+                processLoginSuccess( pwmRequest, true );
+                return;
+            }
+            else
+            {
+                LOGGER.trace( pwmRequest, () -> "configuration password is not correct" );
+                pwmApplication.getIntruderManager().convenience().markAddressAndSession( pwmRequest.getPwmSession() );
+                pwmApplication.getIntruderManager().mark( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmRequest.getSessionLabel() );
+                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_PASSWORD_ONLY_BAD );
+                updateLoginHistory( pwmRequest, pwmRequest.getUserInfoIfLoggedIn(), false );
+                setLastError( pwmRequest, errorInformation );
+                forwardToJsp( pwmRequest );
+                return;
+            }
+        }
+    }
+
+
+    @Override
+    protected ConfigManagerLoginAction readProcessAction( final PwmRequest request )
+            throws PwmUnrecoverableException
+    {
+        try
+        {
+            return ConfigManagerLoginAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
+        }
+        catch ( IllegalArgumentException e )
+        {
+            return null;
+        }
+    }
+
+
+    private static void forwardToJsp( final PwmRequest pwmRequest )
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        final int persistentSeconds = figureMaxLoginSeconds( pwmRequest );
+        final String time = TimeDuration.of( persistentSeconds, TimeDuration.Unit.SECONDS ).asLongString( pwmRequest.getLocale() );
+
+        final ConfigLoginHistory configLoginHistory = readConfigLoginHistory( pwmRequest );
+
+        pwmRequest.setAttribute( PwmRequestAttribute.ConfigLoginHistory, configLoginHistory );
+        pwmRequest.setAttribute( PwmRequestAttribute.ConfigPasswordRememberTime, time );
+        pwmRequest.forwardToJsp( JspUrl.CONFIG_MANAGER_LOGIN );
+    }
+
+
+    private static ConfigLoginHistory readConfigLoginHistory( final PwmRequest pwmRequest )
+    {
+        final ConfigLoginHistory configLoginHistory = pwmRequest.getPwmApplication().readAppAttribute( PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, ConfigLoginHistory.class );
+        return configLoginHistory == null
+                ? new ConfigLoginHistory()
+                : configLoginHistory;
+    }
+
+    private static void updateLoginHistory( final PwmRequest pwmRequest, final UserIdentity userIdentity, final boolean successful )
+    {
+        final ConfigLoginHistory configLoginHistory = readConfigLoginHistory( pwmRequest );
+        final ConfigLoginEvent event = new ConfigLoginEvent(
+                userIdentity == null ? "n/a" : userIdentity.toDisplayString(),
+                Instant.now(),
+                pwmRequest.getPwmSession().getSessionStateBean().getSrcAddress()
+        );
+        final int maxEvents = Integer.parseInt( pwmRequest.getPwmApplication().getConfig().readAppProperty( AppProperty.CONFIG_HISTORY_MAX_ITEMS ) );
+        configLoginHistory.addEvent( event, maxEvents, successful );
+        pwmRequest.getPwmApplication().writeAppAttribute( PwmApplication.AppAttribute.CONFIG_LOGIN_HISTORY, configLoginHistory );
+    }
+
+    @Value
+    public static class ConfigLoginHistory implements Serializable
+    {
+        private List<ConfigLoginEvent> successEvents = new ArrayList<>();
+        private List<ConfigLoginEvent> failedEvents = new ArrayList<>();
+
+        void addEvent( final ConfigLoginEvent event, final int maxEvents, final boolean successful )
+        {
+            final List<ConfigLoginEvent> events = successful ? successEvents : failedEvents;
+            events.add( event );
+            if ( maxEvents > 0 )
+            {
+                while ( events.size() > maxEvents )
+                {
+                    events.remove( 0 );
+                }
+            }
+        }
+
+        public List<ConfigLoginEvent> successEvents( )
+        {
+            return Collections.unmodifiableList( successEvents );
+        }
+
+        public List<ConfigLoginEvent> failedEvents( )
+        {
+            return Collections.unmodifiableList( failedEvents );
+        }
+    }
+
+    @Value
+    public static class ConfigLoginEvent implements Serializable
+    {
+        private final String userIdentity;
+        private final Instant date;
+        private final String networkAddress;
+    }
+
+    private static ProcessStatus processLoginSuccess( final PwmRequest pwmRequest, final boolean persistentLoginEnabled )
+            throws PwmUnrecoverableException, IOException
+    {
+        final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+
+        configManagerBean.setPasswordVerified( true );
+        pwmApplication.getIntruderManager().convenience().clearAddressAndSession( pwmSession );
+        pwmApplication.getIntruderManager().clear( RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME );
+        pwmRequest.getPwmSession().getSessionStateBean().setSessionIdRecycleNeeded( true );
+        if ( persistentLoginEnabled && "on".equals( pwmRequest.readParameterAsString( "remember" ) ) )
+        {
+            writePersistentLoginCookie( pwmRequest );
+        }
+
+        if ( configManagerBean.getPrePasswordEntryUrl() != null )
+        {
+            final String originalUrl = configManagerBean.getPrePasswordEntryUrl();
+            configManagerBean.setPrePasswordEntryUrl( null );
+            pwmRequest.getPwmResponse().sendRedirect( originalUrl );
+            return ProcessStatus.Halt;
+        }
+
+        pwmRequest.sendRedirect( pwmRequest.getURLwithQueryString() );
+        return ProcessStatus.Continue;
+    }
+
+    private static void forwardToNextUrl( final PwmRequest pwmRequest )
+            throws IOException, PwmUnrecoverableException
+    {
+        final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
+
+        if ( configManagerBean.getPrePasswordEntryUrl() != null )
+        {
+            final String originalUrl = configManagerBean.getPrePasswordEntryUrl();
+            configManagerBean.setPrePasswordEntryUrl( null );
+            pwmRequest.getPwmResponse().sendRedirect( originalUrl );
+            return;
+        }
+
+        pwmRequest.sendRedirect( PwmServletDefinition.ConfigManager );
+    }
+
+
+    public static void writePersistentLoginCookie( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final int persistentSeconds = figureMaxLoginSeconds( pwmRequest );
+
+        if ( persistentSeconds > 0 )
+        {
+            final TimeDuration persistenceDuration = TimeDuration.of( persistentSeconds, TimeDuration.Unit.SECONDS );
+            final Instant expirationDate = persistenceDuration.incrementFromInstant( Instant.now() );
+            final StoredConfigurationImpl storedConfig = pwmRequest.getConfig().getStoredConfiguration();
+            final String persistentLoginValue = makePersistentLoginPassword( pwmRequest, storedConfig );
+            final PersistentLoginInfo persistentLoginInfo = new PersistentLoginInfo( expirationDate, persistentLoginValue );
+            final String cookieValue = pwmRequest.getPwmApplication().getSecureService().encryptObjectToString( persistentLoginInfo );
+            pwmRequest.getPwmResponse().writeCookie(
+                    COOKIE_NAME,
+                    cookieValue,
+                    persistentSeconds,
+                    COOKIE_PATH
+            );
+            LOGGER.debug( pwmRequest, () -> "set persistent config login cookie (expires "
+                    + JavaHelper.toIsoDate( expirationDate )
+                    + ")"
+            );
+        }
+    }
+
+    private static void checkPersistentLoginCookie(
+            final PwmRequest pwmRequest
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( !persistentLoginEnabled( pwmRequest ) )
+        {
+            return;
+        }
+
+        final ConfigurationReader runningConfigReader = ContextManager.getContextManager( pwmRequest.getHttpServletRequest().getSession() ).getConfigReader();
+        final StoredConfigurationImpl storedConfig = runningConfigReader.getStoredConfiguration();
+
+        try
+        {
+            final String cookieValue = pwmRequest.readCookie( COOKIE_NAME );
+            if ( !StringUtil.isEmpty( cookieValue ) )
+            {
+                final PersistentLoginInfo persistentLoginInfo = pwmRequest.getPwmApplication().getSecureService().decryptObject( cookieValue, PersistentLoginInfo.class );
+                if ( persistentLoginInfo != null )
+                {
+                    if ( persistentLoginInfo.getExpireDate().isAfter( Instant.now() ) )
+                    {
+                        final String persistentLoginPassword = makePersistentLoginPassword( pwmRequest, storedConfig );
+                        if ( StringUtil.nullSafeEquals( persistentLoginPassword, persistentLoginInfo.getPassword() ) )
+                        {
+                            LOGGER.debug( pwmRequest, () -> "accepting persistent config login from cookie (expires "
+                                    + JavaHelper.toIsoDate( persistentLoginInfo.getExpireDate() )
+                                    + ")"
+                            );
+
+                            final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
+                            configManagerBean.setPasswordVerified( true );
+                        }
+                    }
+
+                    pwmRequest.getPwmResponse().removeCookie( COOKIE_NAME, COOKIE_PATH );
+                    LOGGER.debug( pwmRequest, () -> "removing non-working persistent config login cookie" );
+                }
+            }
+        }
+        catch ( Exception e )
+        {
+            LOGGER.error( pwmRequest, "error examining persistent config login cookie: " + e.getMessage() );
+        }
+    }
+
+
+    @Value
+    private static class PersistentLoginInfo implements Serializable
+    {
+        @SerializedName( "e" )
+        private Instant expireDate;
+
+        @SerializedName( "p" )
+        private String password;
+    }
+
+    public static int figureMaxLoginSeconds( final PwmRequest pwmRequest )
+    {
+        return JavaHelper.silentParseInt(
+                pwmRequest.getConfig().readAppProperty( AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS ),
+                (int) TimeDuration.HOUR.as( TimeDuration.Unit.SECONDS )
+        );
+    }
+
+
+    private static String makePersistentLoginPassword(
+            final PwmRequest pwmRequest,
+            final StoredConfiguration storedConfig
+    )
+            throws PwmUnrecoverableException
+    {
+        final int hashChars = 32;
+        String hashValue = storedConfig.readConfigProperty( ConfigurationProperty.PASSWORD_HASH );
+
+        if ( PwmApplicationMode.RUNNING == pwmRequest.getPwmApplication().getApplicationMode() )
+        {
+            final PwmSession pwmSession = pwmRequest.getPwmSession();
+            hashValue += pwmSession.getUserInfo().getUserIdentity().toDelimitedKey();
+        }
+
+        return StringUtil.truncate( SecureEngine.hash( hashValue, PwmHashAlgorithm.SHA512 ), hashChars );
+    }
+
+
+    private static boolean persistentLoginEnabled(
+            final PwmRequest pwmRequest
+    )
+    {
+        if ( pwmRequest.getConfig().isDefaultValue( PwmSetting.PWM_SECURITY_KEY ) )
+        {
+            LOGGER.debug( pwmRequest, () -> "security not available, persistent login not possible." );
+            return false;
+        }
+
+        return true;
+    }
+}

+ 18 - 13
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -39,11 +39,13 @@ import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmResponse;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.http.filter.ConfigAccessFilter;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.configguide.ConfigGuideUtils;
@@ -121,9 +123,23 @@ public class ConfigManagerServlet extends AbstractPwmServlet
         }
     }
 
+    public static void verifyConfigAccess( final PwmRequest pwmRequest )
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigManagerBean.class );
+        final ProcessStatus processStatus = ConfigAccessFilter.checkAuthentication( pwmRequest, configManagerBean );
+        if ( processStatus != ProcessStatus.Continue )
+        {
+            final String msg = "config access authentication not yet completed";
+            throw PwmUnrecoverableException.newException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, msg );
+        }
+    }
+
     protected void processAction( final PwmRequest pwmRequest )
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
     {
+        verifyConfigAccess( pwmRequest );
+
         final ConfigManagerAction processAction = readProcessAction( pwmRequest );
         if ( processAction != null )
         {
@@ -220,22 +236,11 @@ public class ConfigManagerServlet extends AbstractPwmServlet
             return;
         }
 
-        if ( !pwmSession.isAuthenticated() )
+        if ( !pwmSession.isAuthenticated()
+                || !pwmSession.getSessionManager().checkPermission( pwmApplication, Permission.PWMADMIN ) )
         {
             final ErrorInformation errorInfo = new ErrorInformation(
                     PwmError.ERROR_AUTHENTICATION_REQUIRED,
-                    "You must be authenticated before restricting the configuration"
-            );
-            final RestResultBean restResultBean = RestResultBean.fromError( errorInfo, pwmRequest );
-            LOGGER.debug( pwmSession, errorInfo );
-            pwmRequest.outputJsonResult( restResultBean );
-            return;
-        }
-
-        if ( !pwmSession.getSessionManager().checkPermission( pwmApplication, Permission.PWMADMIN ) )
-        {
-            final ErrorInformation errorInfo = new ErrorInformation(
-                    PwmError.ERROR_UNAUTHORIZED,
                     "You must be authenticated with admin privileges before restricting the configuration"
             );
             final RestResultBean restResultBean = RestResultBean.fromError( errorInfo, pwmRequest );

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

@@ -20,7 +20,6 @@
 
 package password.pwm.http.servlet.configmanager;
 
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Builder;
 import lombok.Value;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
@@ -102,8 +101,9 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
     }
 
     protected void processAction( final PwmRequest pwmRequest )
-            throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException
+            throws ServletException, IOException, PwmUnrecoverableException
     {
+        ConfigManagerServlet.verifyConfigAccess( pwmRequest );
 
         final ConfigManagerAction processAction = readProcessAction( pwmRequest );
         if ( processAction != null )
@@ -201,7 +201,7 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
     }
 
     void restReadWordlistData( final PwmRequest pwmRequest )
-            throws IOException
+            throws IOException, PwmUnrecoverableException
     {
         final LinkedHashMap<WordlistType, WordlistDataBean> outputData = new LinkedHashMap<>();
 
@@ -252,13 +252,13 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
                                 "Population Timestamp",
                                 JavaHelper.toIsoDate( wordlistStatus.getStoreDate() ) ) );
                     }
-                    if ( wordlistStatus.getRemoteInfo() != null && !StringUtil.isEmpty( wordlistStatus.getRemoteInfo().getChecksum() ) )
+                    if ( wordlistStatus.getRemoteInfo() != null && !StringUtil.isEmpty( wordlistStatus.getRemoteInfo().getHash() ) )
                     {
                         presentableValues.add( new DisplayElement(
                                 wordlistType.name() + "_sha256Hash",
                                 DisplayElement.Type.string,
-                                "CRC Checksum",
-                                wordlistStatus.getRemoteInfo().getChecksum() ) );
+                                "SHA1 Checksum",
+                                wordlistStatus.getRemoteInfo().getHash() ) );
                     }
                 }
                 if ( wordlist.getAutoImportError() != null )

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

@@ -40,7 +40,6 @@ import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.option.RecoveryAction;
 import password.pwm.config.option.RecoveryMinLifetimeOption;
 import password.pwm.config.profile.ForgottenPasswordProfile;
-import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
@@ -78,13 +77,11 @@ import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.CaptchaUtility;
-import password.pwm.util.PostChangePasswordAction;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.operations.ActionExecutor;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.cr.NMASCrOperator;
 import password.pwm.util.operations.otp.OTPUserRecord;
@@ -93,8 +90,6 @@ import password.pwm.ws.server.RestResultBean;
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -1221,9 +1216,6 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             pwmApplication.getAuditManager().submit( AuditEvent.RECOVER_PASSWORD, pwmSession.getUserInfo(),
                     pwmSession );
 
-            // add the post-forgotten password actions
-            addPostChangeAction( pwmRequest, userIdentity );
-
             // mark user as requiring a new password.
             pwmSession.getLoginInfoBean().getLoginFlags().add( LoginInfoBean.LoginFlag.forcePwChange );
 
@@ -1242,81 +1234,6 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         }
     }
 
-
-    static void addPostChangeAction(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-    {
-        final PostChangePasswordAction postAction = new PostChangeAction( pwmRequest.getPwmApplication(), userIdentity );
-        pwmRequest.getPwmSession().getUserSessionDataCacheBean().addPostChangePasswordActions( "forgottenPasswordPostActions", postAction );
-    }
-
-    private static class PostChangeAction implements PostChangePasswordAction, Serializable
-    {
-
-        private final transient PwmApplication pwmApplication;
-        private final transient UserIdentity userIdentity;
-
-        PostChangeAction( final PwmApplication pwmApplication, final UserIdentity userIdentity )
-        {
-            this.pwmApplication = pwmApplication;
-            this.userIdentity = userIdentity;
-        }
-
-        private void readObject( final ObjectInputStream in ) throws IOException, ClassNotFoundException
-        {
-            throw new IllegalStateException( "this class does not support deserialization" );
-        }
-
-        @Override
-        public String getLabel( )
-        {
-            return "Forgotten Password Post Actions";
-        }
-
-        @Override
-        public boolean doAction( final PwmSession pwmSession, final String newPassword )
-                throws PwmUnrecoverableException
-        {
-            try
-            {
-                {
-                    // execute configured actions
-                    final ChaiUser proxiedUser = pwmApplication.getProxiedChaiUser( userIdentity );
-                    LOGGER.debug( pwmSession, () -> "executing post-forgotten password configured actions to user " + proxiedUser.getEntryDN() );
-                    final List<ActionConfiguration> configValues = pwmApplication.getConfig().readSettingAsAction( PwmSetting.FORGOTTEN_USER_POST_ACTIONS );
-                    final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings( pwmApplication, userIdentity )
-                            .setMacroMachine( pwmSession.getSessionManager().getMacroMachine( pwmApplication ) )
-                            .setExpandPwmMacros( true )
-                            .createActionExecutor();
-
-                    actionExecutor.executeActions( configValues, pwmSession.getLabel() );
-                }
-            }
-            catch ( PwmOperationalException e )
-            {
-                final ErrorInformation info = new ErrorInformation(
-                        PwmError.ERROR_INTERNAL,
-                        e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues()
-                );
-                final PwmUnrecoverableException newException = new PwmUnrecoverableException( info );
-                newException.initCause( e );
-                throw newException;
-            }
-            catch ( ChaiUnavailableException e )
-            {
-                final String errorMsg = "unable to reach ldap server while writing post-forgotten password attributes: " + e.getMessage();
-                final ErrorInformation info = new ErrorInformation( PwmError.ERROR_ACTIVATION_FAILURE, errorMsg );
-                final PwmUnrecoverableException newException = new PwmUnrecoverableException( info );
-                newException.initCause( e );
-                throw newException;
-            }
-            return true;
-        }
-    }
-
-
     private void handleUserVerificationBadAttempt(
             final PwmRequest pwmRequest,
             final ForgottenPasswordBean forgottenPasswordBean,

+ 6 - 11
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -45,7 +45,7 @@ import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.option.RecoveryAction;
 import password.pwm.config.option.RecoveryMinLifetimeOption;
 import password.pwm.config.profile.ForgottenPasswordProfile;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.ProfileUtility;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
@@ -67,11 +67,11 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.token.TokenType;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
-import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
+import password.pwm.util.password.RandomPasswordGenerator;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
@@ -96,8 +96,7 @@ public class ForgottenPasswordUtil
     {
         final ForgottenPasswordBean.RecoveryFlags recoveryFlags = forgottenPasswordBean.getRecoveryFlags();
         final ForgottenPasswordBean.Progress progress = forgottenPasswordBean.getProgress();
-        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>();
-        result.addAll( recoveryFlags.getOptionalAuthMethods() );
+        final Set<IdentityVerificationMethod> result = new LinkedHashSet<>( recoveryFlags.getOptionalAuthMethods() );
         result.removeAll( progress.getSatisfiedMethods() );
 
         for ( final IdentityVerificationMethod recoveryVerificationMethods : new LinkedHashSet<>( result ) )
@@ -115,7 +114,7 @@ public class ForgottenPasswordUtil
         return Collections.unmodifiableSet( result );
     }
 
-    public static RecoveryAction getRecoveryAction( final Configuration configuration, final ForgottenPasswordBean forgottenPasswordBean )
+    static RecoveryAction getRecoveryAction( final Configuration configuration, final ForgottenPasswordBean forgottenPasswordBean )
     {
         final ForgottenPasswordProfile forgottenPasswordProfile = configuration.getForgottenPasswordProfiles().get( forgottenPasswordBean.getForgottenPasswordProfileID() );
         return forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_ACTION, RecoveryAction.class );
@@ -189,7 +188,7 @@ public class ForgottenPasswordUtil
             final CommonValues commonValues,
             final ForgottenPasswordBean forgottenPasswordBean
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
+            throws PwmUnrecoverableException
     {
         final PwmApplication pwmApplication = commonValues.getPwmApplication();
         final Configuration config = commonValues.getConfig();
@@ -219,7 +218,6 @@ public class ForgottenPasswordUtil
     }
 
     static boolean checkAuthRecord( final PwmRequest pwmRequest, final String userGuid )
-            throws PwmUnrecoverableException
     {
         if ( userGuid == null || userGuid.isEmpty() )
         {
@@ -463,9 +461,6 @@ public class ForgottenPasswordUtil
             LOGGER.info( pwmRequest, () -> "user successfully supplied password recovery responses, emailing new password to: "
                     + theUser.getEntryDN() );
 
-            // add post change actions
-            ForgottenPasswordServlet.addPostChangeAction( pwmRequest, userIdentity );
-
             // create new password
             final PasswordData newPassword = RandomPasswordGenerator.createRandomPassword(
                     pwmRequest.getSessionLabel(),
@@ -639,7 +634,7 @@ public class ForgottenPasswordUtil
                 pwmApplication,
                 sessionLabel,
                 userIdentity,
-                ProfileType.ForgottenPassword
+                ProfileDefinition.ForgottenPassword
         );
 
         if ( StringUtil.isEmpty( forgottenProfileID ) )

+ 14 - 13
server/src/main/java/password/pwm/http/servlet/forgottenpw/RemoteVerificationMethod.java

@@ -35,9 +35,9 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.ldap.UserInfo;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
@@ -114,9 +114,10 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
     }
 
     @Override
-    public void init( final PwmApplication pwmApplication, final UserInfo userInfo, final SessionLabel sessionLabel, final Locale locale ) throws PwmUnrecoverableException
+    public void init( final PwmApplication pwmApplication, final UserInfo userInfo, final SessionLabel sessionLabel, final Locale locale )
+            throws PwmUnrecoverableException
     {
-        pwmHttpClient = new PwmHttpClient( pwmApplication, sessionLabel );
+        pwmHttpClient = pwmApplication.getHttpClientService().getPwmHttpClient( );
         this.remoteSessionID = pwmApplication.getSecureService().pwmRandom().randomUUID().toString();
         this.userInfo = userInfo;
         this.sessionLabel = sessionLabel;
@@ -141,7 +142,7 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
         lastResponse = null;
 
         final Map<String, String> headers = new LinkedHashMap<>();
-        headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValue() );
+        headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.json.getHeaderValueWithEncoding() );
         headers.put( HttpHeader.AcceptLanguage.getHttpName(), locale.toLanguageTag() );
 
         final RemoteVerificationRequestBean remoteVerificationRequestBean = new RemoteVerificationRequestBean();
@@ -150,16 +151,16 @@ public class RemoteVerificationMethod implements VerificationMethodSystem
         remoteVerificationRequestBean.setUserInfo( PublicUserInfoBean.fromUserInfoBean( userInfo, pwmApplication.getConfig(), locale, macroMachine ) );
         remoteVerificationRequestBean.setUserResponses( userResponses );
 
-        final PwmHttpClientRequest pwmHttpClientRequest = new PwmHttpClientRequest(
-                HttpMethod.POST,
-                url,
-                JsonUtil.serialize( remoteVerificationRequestBean ),
-                headers
-        );
+        final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
+                .method( HttpMethod.POST )
+                .url( url )
+                .body( JsonUtil.serialize( remoteVerificationRequestBean ) )
+                .headers( headers )
+                .build();
 
         try
         {
-            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( pwmHttpClientRequest, this.sessionLabel );
             final String responseBodyStr = response.getBody();
             this.lastResponse = JsonUtil.deserialize( responseBodyStr, RemoteVerificationResponseBean.class );
         }

+ 3 - 58
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskCardInfoBean.java

@@ -25,22 +25,16 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import lombok.Builder;
 import lombok.Value;
 import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.HelpdeskProfile;
-import password.pwm.config.profile.LdapProfile;
-import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmURL;
-import password.pwm.http.servlet.PwmServletDefinition;
-import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.http.servlet.peoplesearch.PhotoDataReader;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
@@ -86,11 +80,11 @@ public class HelpdeskCardInfoBean implements Serializable
                 userIdentity,
                 theUser.getChaiProvider()
         );
-        final MacroMachine macroMachine = MacroMachine.forUser( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfo, null );
 
         builder.userKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
 
-        builder.photoURL( figurePhotoURL( pwmRequest, helpdeskProfile, theUser, macroMachine, userIdentity ) );
+        final PhotoDataReader photoDataReader = HelpdeskServlet.photoDataReader( pwmRequest, helpdeskProfile, userIdentity );
+        builder.photoURL( photoDataReader.figurePhotoURL( ) );
 
         builder.displayNames( figureDisplayNames( pwmRequest.getPwmApplication(), helpdeskProfile, pwmRequest.getSessionLabel(), userInfo ) );
 
@@ -140,53 +134,4 @@ public class HelpdeskCardInfoBean implements Serializable
         }
         return displayLabels;
     }
-
-    private static String figurePhotoURL(
-            final PwmRequest pwmRequest,
-            final HelpdeskProfile helpdeskProfile,
-            final ChaiUser chaiUser,
-            final MacroMachine macroMachine,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final boolean enabled = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_PHOTOS );
-
-        if ( !enabled )
-        {
-            LOGGER.debug( pwmRequest, () -> "detailed user data lookup for " + userIdentity.toString() + ", failed photo query filter, denying photo view" );
-            return null;
-        }
-
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile(  pwmApplication.getConfig() );
-
-        final String overrideURL = ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_PHOTO_URL_OVERRIDE );
-        try
-        {
-            if ( !StringUtil.isEmpty( overrideURL ) )
-            {
-                return macroMachine.expandMacros( overrideURL );
-            }
-
-            try
-            {
-                LdapOperationsHelper.readPhotoDataFromLdap( pwmApplication.getConfig(), chaiUser, userIdentity );
-            }
-            catch ( PwmOperationalException e )
-            {
-                LOGGER.debug( pwmRequest, () -> "determined " + userIdentity + " does not have photo data available while generating detail data" );
-                return null;
-            }
-        }
-        catch ( ChaiUnavailableException e )
-        {
-            throw PwmUnrecoverableException.fromChaiException( e );
-        }
-
-        String returnUrl = pwmRequest.getContextPath() + PwmServletDefinition.Helpdesk.servletUrl();
-        returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_ACTION_REQUEST, HelpdeskServlet.HelpdeskAction.photo.name() );
-        returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_USERKEY,  userIdentity.toObfuscatedKey( pwmApplication ) );
-        return returnUrl;
-    }
 }

+ 20 - 24
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -57,6 +57,7 @@ import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
+import password.pwm.http.servlet.peoplesearch.PhotoDataReader;
 import password.pwm.http.servlet.peoplesearch.SearchRequestBean;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapOperationsHelper;
@@ -75,7 +76,6 @@ import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.token.TokenService;
 import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
-import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -87,6 +87,7 @@ import password.pwm.util.operations.CrService;
 import password.pwm.util.operations.OtpService;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.otp.OTPUserRecord;
+import password.pwm.util.password.RandomPasswordGenerator;
 import password.pwm.util.secure.SecureService;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.RestCheckPasswordServer;
@@ -95,9 +96,7 @@ import password.pwm.ws.server.rest.RestSetPasswordServer;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.time.Instant;
 import java.util.Collection;
 import java.util.Collections;
@@ -107,6 +106,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Callable;
 
 /**
  * Admin interaction servlet for reset user passwords.
@@ -1371,30 +1371,12 @@ public class HelpdeskServlet extends ControlledPwmServlet
         final UserIdentity userIdentity = readUserKeyRequestParameter( pwmRequest );
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
         HelpdeskServletUtil.checkIfUserIdentityViewable( pwmRequest, helpdeskProfile, userIdentity  );
-        final ChaiUser chaiUser = getChaiUser( pwmRequest, helpdeskProfile, userIdentity );
+        final PhotoDataReader photoDataReader = photoDataReader( pwmRequest, helpdeskProfile, userIdentity );
 
         LOGGER.debug( pwmRequest, () -> "received user photo request to view user " + userIdentity.toString() );
 
-        final PhotoDataBean photoData;
-        try
-        {
-            photoData = LdapOperationsHelper.readPhotoDataFromLdap( pwmRequest.getConfig(), chaiUser, userIdentity );
-        }
-        catch ( PwmOperationalException e )
-        {
-            final ErrorInformation errorInformation = e.getErrorInformation();
-            LOGGER.error( pwmRequest, errorInformation );
-            pwmRequest.respondWithError( errorInformation, false );
-            return ProcessStatus.Halt;
-        }
-
-        try ( OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream() )
-        {
-            final HttpServletResponse resp = pwmRequest.getPwmResponse().getHttpServletResponse();
-            resp.setContentType( photoData.getMimeType() );
-
-            outputStream.write( photoData.getContents().copyOf() );
-        }
+        final Callable<Optional<PhotoDataBean>> callablePhotoReader = photoDataReader::readPhotoData;
+        PhotoDataReader.servletRespondWithPhoto( pwmRequest, callablePhotoReader );
         return ProcessStatus.Halt;
     }
 
@@ -1410,4 +1392,18 @@ public class HelpdeskServlet extends ControlledPwmServlet
         }
         return UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
     }
+
+    static PhotoDataReader photoDataReader( final PwmRequest pwmRequest, final HelpdeskProfile helpdeskProfile, final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+
+        final boolean enabled = helpdeskProfile.readSettingAsBoolean( PwmSetting.HELPDESK_ENABLE_PHOTOS );
+        final PhotoDataReader.Settings settings = PhotoDataReader.Settings.builder()
+                .enabled( enabled )
+                .photoPermissions( null )
+                .chaiProvider( getChaiUser( pwmRequest, helpdeskProfile, userIdentity ).getChaiProvider() )
+                .build();
+
+        return new PhotoDataReader( pwmRequest, settings, userIdentity );
+    }
 }

+ 14 - 8
server/src/main/java/password/pwm/http/servlet/oauth/OAuthMachine.java

@@ -36,10 +36,10 @@ import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
-import password.pwm.http.client.PwmHttpClient;
-import password.pwm.http.client.PwmHttpClientConfiguration;
-import password.pwm.http.client.PwmHttpClientRequest;
-import password.pwm.http.client.PwmHttpClientResponse;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.java.JavaHelper;
@@ -281,20 +281,26 @@ public class OAuthMachine
                 headers.put( HttpHeader.Authorization.getHttpName(),
                         "Bearer " + accessToken );
             }
-            headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.form.getHeaderValue() );
+            headers.put( HttpHeader.ContentType.getHttpName(), HttpContentType.form.getHeaderValueWithEncoding() );
 
-            pwmHttpClientRequest = new PwmHttpClientRequest( HttpMethod.POST, requestUrl, requestBody, headers );
+            pwmHttpClientRequest = PwmHttpClientRequest.builder()
+                    .method( HttpMethod.POST )
+                    .url( requestUrl )
+                    .body( requestBody )
+                    .headers( headers )
+                    .build();
         }
 
         final PwmHttpClientResponse pwmHttpClientResponse;
         try
         {
             final PwmHttpClientConfiguration config = PwmHttpClientConfiguration.builder()
+                    .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.configuredCertificates )
                     .certificates( JavaHelper.isEmpty( certs ) ? null : certs )
                     .maskBodyDebugOutput( true )
                     .build();
-            final PwmHttpClient pwmHttpClient = new PwmHttpClient( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), config );
-            pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest );
+            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient( config );
+            pwmHttpClientResponse = pwmHttpClient.makeRequest( pwmHttpClientRequest, pwmRequest.getSessionLabel() );
         }
         catch ( PwmException e )
         {

+ 78 - 59
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java

@@ -21,16 +21,14 @@
 package password.pwm.http.servlet.peoplesearch;
 
 import password.pwm.AppProperty;
-import password.pwm.PwmApplication;
-import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.PwmRequest;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 
 import java.util.Collections;
@@ -40,155 +38,176 @@ import java.util.Set;
 
 public class PeopleSearchConfiguration
 {
-    private final PwmRequest pwmRequest;
-    private final PwmApplication pwmApplication;
+    private final PeopleSearchProfile peopleSearchProfile;
+    private final Configuration configuration;
 
-
-    private PeopleSearchConfiguration( final PwmRequest pwmRequest )
-    {
-        this.pwmRequest = pwmRequest;
-        this.pwmApplication = pwmRequest.getPwmApplication();
-    }
-
-    public String getPhotoAttribute( final UserIdentity userIdentity )
+    PeopleSearchConfiguration( final Configuration configuration, final PeopleSearchProfile peopleSearchProfile )
+            throws PwmUnrecoverableException
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
-        return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_PHOTO );
+        this.configuration = configuration;
+        this.peopleSearchProfile = peopleSearchProfile;
     }
 
-    String getPhotoUrlOverride( final UserIdentity userIdentity )
+    String getEmailAttribute( final UserIdentity userIdentity )
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
-        return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_PHOTO_URL_OVERRIDE );
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
+        return ldapProfile.readSettingAsString( PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
     }
 
-    public boolean isPhotosEnabled( final UserIdentity actor, final SessionLabel sessionLabel )
-            throws PwmUnrecoverableException
+    public boolean isPhotosEnabled( )
     {
-        if ( actor == null )
-        {
-            return false;
-        }
-
-        final boolean settingEnabled = pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PHOTO );
-        final String photoAttribute = getPhotoAttribute( actor );
-        final String photoUrl = getPhotoUrlOverride( actor );
-        return settingEnabled
-                && ( !StringUtil.isEmpty( photoAttribute ) || !StringUtil.isEmpty( photoUrl ) );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PHOTO );
     }
 
     public boolean isOrgChartEnabled()
     {
-        final Configuration config = pwmApplication.getConfig();
-        return config.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ORGCHART );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ORGCHART );
     }
 
     String getOrgChartParentAttr( final UserIdentity userIdentity )
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
         return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_ORGCHART_PARENT );
     }
 
     String getOrgChartChildAttr( final UserIdentity userIdentity  )
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
         return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_ORGCHART_CHILD );
     }
 
     String getOrgChartAssistantAttr( final UserIdentity userIdentity  )
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
         return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_ORGCHART_ASSISTANT );
     }
 
     String getOrgChartWorkforceIDAttr( final UserIdentity userIdentity  )
     {
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
         return ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_ORGCHART_WORKFORCEID );
     }
 
     public boolean isOrgChartShowChildCount()
     {
-        return Boolean.parseBoolean( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_ORGCHART_ENABLE_CHILD_COUNT ) );
+        return Boolean.parseBoolean( configuration.readAppProperty( AppProperty.PEOPLESEARCH_ORGCHART_ENABLE_CHILD_COUNT ) );
     }
 
     public int getOrgChartMaxParents()
     {
-        return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_ORGCHART_MAX_PARENTS ) );
+        return Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_ORGCHART_MAX_PARENTS ) );
     }
 
     public boolean isEnableExportCsv()
     {
-        return pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_EXPORT );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_EXPORT );
     }
 
     public int getExportCsvMaxDepth()
     {
-        return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_DEPTH ) );
+        return Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_DEPTH ) );
     }
 
     public boolean isEnableMailtoLinks()
     {
-        return pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_TEAM_MAILTO );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_TEAM_MAILTO );
     }
 
     public int getMailtoLinksMaxDepth( )
     {
-        return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_DEPTH ) );
+        return Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_DEPTH ) );
+    }
+
+    TimeDuration getMaxCacheTime()
+    {
+        final long seconds = peopleSearchProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS );
+        return TimeDuration.of( seconds, TimeDuration.Unit.SECONDS );
     }
 
     TimeDuration getExportCsvMaxDuration( )
     {
-        final int seconds = Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_SECONDS ) );
+        final int seconds = Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_SECONDS ) );
         return TimeDuration.of( seconds, TimeDuration.Unit.SECONDS );
     }
 
     int getExportCsvMaxThreads( )
     {
-        return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_THREADS ) );
+        return Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_THREADS ) );
     }
 
     int getExportCsvMaxItems( )
     {
-        return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_ITEMS ) );
+        return Integer.parseInt( configuration.readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_ITEMS ) );
+    }
+
+    public String getSearchFilter()
+    {
+        return peopleSearchProfile.readSettingAsString( PwmSetting.PEOPLE_SEARCH_SEARCH_FILTER );
+    }
+
+    public List<UserPermission> getSearchPhotoFilter()
+    {
+        return peopleSearchProfile.readSettingAsUserPermission( PwmSetting.PEOPLE_SEARCH_PHOTO_QUERY_FILTER );
     }
 
     public List<FormConfiguration> getSearchForm()
     {
-        return pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_SEARCH_FORM );
+        return peopleSearchProfile.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_SEARCH_FORM );
+    }
+
+    public List<FormConfiguration> getSearchResultForm()
+    {
+        return peopleSearchProfile.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
+    }
+
+    public List<FormConfiguration> getSearchDetailForm()
+    {
+        return peopleSearchProfile.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_DETAIL_FORM );
+    }
+
+    public boolean isUseProxy()
+    {
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_USE_PROXY );
+    }
+
+    public List<String> getLdapBase()
+    {
+        return peopleSearchProfile.readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_SEARCH_BASE );
+    }
+
+    public List<String> getDisplayNameCardLables()
+    {
+        return peopleSearchProfile.readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_DISPLAY_NAMES_CARD_LABELS );
+    }
+
+    public String getDisplayName()
+    {
+        return peopleSearchProfile.readSettingAsString( PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME );
     }
 
     Set<String> getSearchAttributes()
     {
         final List<FormConfiguration> searchForm = getSearchForm();
-
         return Collections.unmodifiableSet( new LinkedHashSet<>( FormConfiguration.convertToListOfNames( searchForm ) ) );
     }
 
     List<FormConfiguration> getResultForm()
     {
-        return pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
+        return peopleSearchProfile.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
     }
 
     int getResultLimit()
     {
-        return ( int ) pwmRequest.getConfig().readSettingAsLong( PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT );
+        return ( int ) peopleSearchProfile.readSettingAsLong( PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT );
     }
 
     public boolean isEnablePrinting()
     {
-        return pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PRINTING );
-    }
-
-    public static PeopleSearchConfiguration forRequest(
-            final PwmRequest pwmRequest
-    )
-    {
-        return new PeopleSearchConfiguration( pwmRequest );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PRINTING );
     }
 
     public boolean isEnableAdvancedSearch()
     {
-        return pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ADVANCED_SEARCH );
+        return peopleSearchProfile.readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ADVANCED_SEARCH );
     }
 }

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

@@ -32,14 +32,13 @@ import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmURL;
 import password.pwm.http.servlet.helpdesk.HelpdeskServletUtil;
 import password.pwm.http.servlet.peoplesearch.bean.AttributeDetailBean;
 import password.pwm.http.servlet.peoplesearch.bean.LinkReferenceBean;
@@ -49,7 +48,6 @@ import password.pwm.http.servlet.peoplesearch.bean.SearchResultBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserDetailBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserReferenceBean;
 import password.pwm.i18n.Display;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.PhotoDataBean;
 import password.pwm.ldap.UserInfo;
@@ -80,6 +78,7 @@ 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.Executor;
@@ -98,12 +97,14 @@ class PeopleSearchDataReader
         attributeRead,
         checkIfViewable,
         searchResultBean,
+        photo,
     }
 
-    PeopleSearchDataReader( final PwmRequest pwmRequest )
+    PeopleSearchDataReader( final PwmRequest pwmRequest, final PeopleSearchProfile peopleSearchProfile )
+            throws PwmUnrecoverableException
     {
         this.pwmRequest = pwmRequest;
-        this.peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
+        this.peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile );
     }
 
     SearchResultBean makeSearchResultBean(
@@ -133,7 +134,7 @@ class PeopleSearchDataReader
                 .toBuilder().fromCache( false ).build();
 
         StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_SEARCHES );
-        storeDataInCache( pwmRequest.getPwmApplication(), cacheKey, searchResultBean );
+        storeDataInCache( cacheKey, searchResultBean );
         LOGGER.trace( pwmRequest, () -> "returning " + searchResultBean.getSearchResults().size()
                 + " results for search request "
                 + JsonUtil.serialize( searchRequestBean ) );
@@ -225,7 +226,7 @@ class PeopleSearchDataReader
         }
 
         final TimeDuration totalTime = TimeDuration.fromCurrent( startTime );
-        storeDataInCache( pwmRequest.getPwmApplication(), cacheKey, orgChartData );
+        storeDataInCache( cacheKey, orgChartData );
         {
             final int finalChildCount = childCount;
             LOGGER.trace( pwmRequest, () -> "completed makeOrgChartData of " + userIdentity.toDisplayString()
@@ -262,16 +263,18 @@ class PeopleSearchDataReader
 
         final UserDetailBean userDetailBean = new UserDetailBean();
         userDetailBean.setUserKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
-        final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_DETAIL_FORM );
-        final Map<String, AttributeDetailBean> attributeBeans = convertResultMapToBeans( pwmRequest, userIdentity, detailFormConfig, searchResults );
+        final List<FormConfiguration> detailFormConfig = this.peopleSearchConfiguration.getSearchDetailForm();
+        final Map<String, AttributeDetailBean> attributeBeans = convertResultMapToBeans( userIdentity, detailFormConfig, searchResults );
 
         userDetailBean.setDetail( attributeBeans );
-        final String photoURL = figurePhotoURL( pwmRequest, userIdentity );
+
+        final PhotoDataReader photoDataReader = photoDataReader( userIdentity );
+        final String photoURL = photoDataReader.figurePhotoURL( );
         if ( photoURL != null )
         {
             userDetailBean.setPhotoURL( photoURL );
         }
-        final List<String> displayName = figureDisplaynames( pwmRequest, userIdentity );
+        final List<String> displayName = figureDisplaynames( userIdentity );
         if ( displayName != null )
         {
             userDetailBean.setDisplayNames( displayName );
@@ -281,7 +284,7 @@ class PeopleSearchDataReader
 
         LOGGER.trace( pwmRequest, () -> "finished building userDetail result of " + userIdentity
                 + " in " + TimeDuration.fromCurrent( startTime ).asCompactString() );
-        storeDataInCache( pwmRequest.getPwmApplication(), cacheKey, userDetailBean );
+        storeDataInCache( cacheKey, userDetailBean );
         return userDetailBean;
     }
 
@@ -385,9 +388,10 @@ class PeopleSearchDataReader
     {
         final OrgChartReferenceBean orgChartReferenceBean = new OrgChartReferenceBean();
         orgChartReferenceBean.setUserKey( userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
-        orgChartReferenceBean.setPhotoURL( figurePhotoURL( pwmRequest, userIdentity ) );
+        final PhotoDataReader photoDataReader = photoDataReader( userIdentity );
+        orgChartReferenceBean.setPhotoURL( photoDataReader.figurePhotoURL( ) );
 
-        final List<String> displayLabels = figureDisplaynames( pwmRequest, userIdentity );
+        final List<String> displayLabels = figureDisplaynames( userIdentity );
         orgChartReferenceBean.setDisplayNames( displayLabels );
 
         return orgChartReferenceBean;
@@ -443,17 +447,17 @@ class PeopleSearchDataReader
         return returnObj;
     }
 
-    private static void storeDataInCache(
-            final PwmApplication pwmApplication,
+    private void storeDataInCache(
             final CacheKey cacheKey,
             final Serializable data
     )
             throws PwmUnrecoverableException
     {
-        final long maxCacheSeconds = pwmApplication.getConfig().readSettingAsLong( PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS );
-        if ( maxCacheSeconds > 0 )
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
+        final TimeDuration maxCacheTime = this.peopleSearchConfiguration.getMaxCacheTime();
+        if ( !maxCacheTime.isZero() )
         {
-            final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS( maxCacheSeconds * 1000 );
+            final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( maxCacheTime );
             pwmApplication.getCacheService().put( cacheKey, cachePolicy, data );
         }
     }
@@ -468,66 +472,12 @@ class PeopleSearchDataReader
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final CacheKey cacheKey = makeCacheKey( operationIdentifier.name(), dataIdentifier );
-        final long maxCacheSeconds = pwmApplication.getConfig().readSettingAsLong( PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS );
-        final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS( maxCacheSeconds * 1000 );
+        final TimeDuration maxCacheTime = this.peopleSearchConfiguration.getMaxCacheTime();
+        final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( maxCacheTime );
         return pwmApplication.getCacheService().get( cacheKey, cachePolicy, classOfT, cacheLoader );
     }
 
-    private String figurePhotoURL(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final Instant startTime = Instant.now();
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final boolean enabled = peopleSearchConfiguration.isPhotosEnabled( pwmRequest.getUserInfoIfLoggedIn(), pwmRequest.getSessionLabel() );
-        if ( !enabled )
-        {
-            return null;
-        }
 
-        {
-            final List<UserPermission> permissions = pwmApplication.getConfig().readSettingAsUserPermission( PwmSetting.PEOPLE_SEARCH_PHOTO_QUERY_FILTER );
-            final boolean hasPermission = LdapPermissionTester.testUserPermissions( pwmApplication, pwmRequest.getSessionLabel(), userIdentity, permissions );
-            if ( !hasPermission )
-            {
-                LOGGER.debug( pwmRequest, () -> "user " + userIdentity + " failed photo query filter, denying photo view ("
-                        + TimeDuration.compactFromCurrent( startTime ) + ")" );
-                return null;
-            }
-        }
-
-        final String overrideURL = peopleSearchConfiguration.getPhotoUrlOverride( userIdentity );
-        try
-        {
-            if ( overrideURL != null && !overrideURL.isEmpty() )
-            {
-                final MacroMachine macroMachine = getMacroMachine( userIdentity );
-                return macroMachine.expandMacros( overrideURL );
-            }
-
-            try
-            {
-                readPhotoDataFromLdap( userIdentity );
-            }
-            catch ( PwmOperationalException e )
-            {
-                LOGGER.debug( pwmRequest, () -> "determined " + userIdentity.toDisplayString() + " does not have photo data available while generating detail data ("
-                        + TimeDuration.compactFromCurrent( startTime ) + ")" );
-                return null;
-            }
-        }
-        catch ( ChaiUnavailableException e )
-        {
-            throw PwmUnrecoverableException.fromChaiException( e );
-        }
-
-        String returnUrl = pwmRequest.getURLwithoutQueryString();
-        returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_ACTION_REQUEST, PeopleSearchServlet.PeopleSearchActions.photo.name() );
-        returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_USERKEY,  userIdentity.toObfuscatedKey( pwmApplication ) );
-        return returnUrl;
-    }
 
     private String figureDisplaynameValue(
             final PwmRequest pwmRequest,
@@ -536,18 +486,17 @@ class PeopleSearchDataReader
             throws PwmUnrecoverableException
     {
         final MacroMachine macroMachine = getMacroMachine( userIdentity );
-        final String settingValue = pwmRequest.getConfig().readSettingAsString( PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME );
+        final String settingValue = this.peopleSearchConfiguration.getDisplayName();
         return macroMachine.expandMacros( settingValue );
     }
 
     private List<String> figureDisplaynames(
-            final PwmRequest pwmRequest,
             final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
     {
         final List<String> displayLabels = new ArrayList<>();
-        final List<String> displayStringSettings = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_DISPLAY_NAMES_CARD_LABELS );
+        final List<String> displayStringSettings = this.peopleSearchConfiguration.getDisplayNameCardLables();
         if ( displayStringSettings != null )
         {
             final MacroMachine macroMachine = getMacroMachine( userIdentity );
@@ -560,8 +509,14 @@ class PeopleSearchDataReader
         return displayLabels;
     }
 
+    Optional<PhotoDataBean> readPhotoData( final UserIdentity userIdentity )
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        final PhotoDataReader photoDataReader = photoDataReader( userIdentity );
+        return photoDataReader.readPhotoData();
+    }
+
     private Map<String, AttributeDetailBean> convertResultMapToBeans(
-            final PwmRequest pwmRequest,
             final UserIdentity userIdentity,
             final List<FormConfiguration> detailForm,
             final Map<String, String> searchResults
@@ -627,18 +582,8 @@ class PeopleSearchDataReader
     }
 
 
-    private ChaiUser getChaiUser(
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final boolean useProxy = useProxy();
-        return useProxy
-                ? pwmRequest.getPwmApplication().getProxiedChaiUser( userIdentity )
-                : pwmRequest.getPwmSession().getSessionManager().getActor( pwmRequest.getPwmApplication(), userIdentity );
-    }
 
-    private MacroMachine getMacroMachine(
+    MacroMachine getMacroMachine(
             final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
@@ -691,7 +636,7 @@ class PeopleSearchDataReader
 
     private String makeSimpleSearchFilter()
     {
-        final String configuredFilter = pwmRequest.getConfig().readSettingAsString( PwmSetting.PEOPLE_SEARCH_SEARCH_FILTER );
+        final String configuredFilter = this.peopleSearchConfiguration.getSearchFilter();
         if ( configuredFilter != null && !configuredFilter.isEmpty() )
         {
             return configuredFilter;
@@ -734,11 +679,21 @@ class PeopleSearchDataReader
 
     private boolean useProxy( )
     {
+        final boolean useProxy = peopleSearchConfiguration.isUseProxy();
+        final boolean publicAccess = pwmRequest.getURL().isPublicUrl();
 
-        final boolean useProxy = pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_USE_PROXY );
-        final boolean publicAccessEnabled = pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC );
+        return useProxy || ( !pwmRequest.isAuthenticated() && publicAccess );
+    }
 
-        return useProxy || !pwmRequest.isAuthenticated() && publicAccessEnabled;
+    ChaiUser getChaiUser(
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final boolean useProxy = useProxy();
+        return useProxy
+                ? pwmRequest.getPwmApplication().getProxiedChaiUser( userIdentity )
+                : pwmRequest.getPwmSession().getSessionManager().getActor( pwmRequest.getPwmApplication(), userIdentity );
     }
 
     private UserSearchResults doDetailLookup(
@@ -746,7 +701,7 @@ class PeopleSearchDataReader
     )
             throws PwmUnrecoverableException
     {
-        final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_DETAIL_FORM );
+        final List<FormConfiguration> detailFormConfig = this.peopleSearchConfiguration.getSearchDetailForm();
         final Map<String, String> attributeHeaderMap = UserSearchResults.fromFormConfiguration(
                 detailFormConfig, pwmRequest.getLocale() );
 
@@ -781,19 +736,6 @@ class PeopleSearchDataReader
         }
     }
 
-    PhotoDataBean readPhotoDataFromLdap(
-            final UserIdentity userIdentity
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
-    {
-        final ChaiUser chaiUser = getChaiUser( userIdentity );
-        return LdapOperationsHelper.readPhotoDataFromLdap(
-                pwmRequest.getConfig(),
-                chaiUser,
-                userIdentity
-        );
-    }
-
     private SearchResultBean makeSearchResultsImpl(
             final SearchRequestBean searchRequest
     )
@@ -810,7 +752,7 @@ class PeopleSearchDataReader
         final SearchConfiguration searchConfiguration;
         {
             final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
-            builder.contexts( pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_SEARCH_BASE ) );
+            builder.contexts( this.peopleSearchConfiguration.getLdapBase() );
             builder.enableContextValidation( false );
             builder.enableValueEscaping( false );
             builder.enableSplitWhitespace( true );
@@ -956,7 +898,7 @@ class PeopleSearchDataReader
             throws PwmUnrecoverableException
     {
         final List<String> returnValues = new ArrayList<>(  );
-        final String mailtoAttr = userIdentity.getLdapProfile( pwmRequest.getConfig() ).readSettingAsString( PwmSetting.EMAIL_USER_MAIL_ATTRIBUTE );
+        final String mailtoAttr = this.peopleSearchConfiguration.getEmailAttribute( userIdentity );
         final String value = readUserAttribute( userIdentity, mailtoAttr );
         if ( !StringUtil.isEmpty( value ) )
         {
@@ -1117,4 +1059,17 @@ class PeopleSearchDataReader
         }
     }
 
+
+    PhotoDataReader photoDataReader( final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+        final PhotoDataReader.Settings settings = PhotoDataReader.Settings.builder()
+                .enabled( peopleSearchConfiguration.isPhotosEnabled() )
+                .photoPermissions( peopleSearchConfiguration.getSearchPhotoFilter() )
+                .chaiProvider( getChaiUser( userIdentity ).getChaiProvider() )
+                .build();
+
+        return new PhotoDataReader( pwmRequest, settings, userIdentity );
+    }
+
 }

+ 0 - 56
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchResourcesServlet.java

@@ -1,56 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2019 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package password.pwm.http.servlet.peoplesearch;
-
-import org.apache.commons.lang3.StringUtils;
-import password.pwm.PwmConstants;
-
-import javax.servlet.ServletException;
-import javax.servlet.annotation.WebServlet;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-@WebServlet(
-        name = "PeopleSearchResourcesServlet",
-        urlPatterns = {
-                PwmConstants.URL_PREFIX_PRIVATE + "/peoplesearch/fonts/*",
-                PwmConstants.URL_PREFIX_PUBLIC + "/peoplesearch/fonts/*"
-        }
-)
-public class PeopleSearchResourcesServlet extends HttpServlet
-{
-
-    @Override
-    protected void service( final HttpServletRequest request, final HttpServletResponse response ) throws ServletException, IOException
-    {
-        response.sendRedirect( String.format(
-                // Build a relative URL with place holders:
-                "%s%s/resources/app/fonts/%s",
-
-                // Place holder values:
-                request.getContextPath(),
-                PwmConstants.URL_PREFIX_PUBLIC,
-                StringUtils.substringAfterLast( request.getRequestURI(), "/" )
-        ) );
-    }
-}

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

@@ -23,11 +23,10 @@ package password.pwm.http.servlet.peoplesearch;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import org.apache.commons.csv.CSVPrinter;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
-import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpHeader;
@@ -48,21 +47,21 @@ import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Callable;
 
 public abstract class PeopleSearchServlet extends ControlledPwmServlet
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( PeopleSearchServlet.class );
 
     private static final String PARAM_USERKEY = "userKey";
@@ -109,15 +108,8 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
     }
 
     @Override
-    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
-    {
-        if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE ) );
-        }
-
-        return ProcessStatus.Continue;
-    }
+    public abstract ProcessStatus preProcessCheck( PwmRequest pwmRequest )
+            throws PwmUnrecoverableException, IOException, ServletException;
 
     @ActionHandler( action = "clientData" )
     private ProcessStatus restLoadClientData(
@@ -126,7 +118,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
     )
             throws PwmUnrecoverableException, IOException
     {
-        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile( pwmRequest ) );
 
         final PeopleSearchClientConfigBean peopleSearchClientConfigBean = PeopleSearchClientConfigBean.fromConfig(
                 pwmRequest,
@@ -148,7 +140,8 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
     {
         final SearchRequestBean searchRequest = JsonUtil.deserialize( pwmRequest.readRequestBodyAsString(), SearchRequestBean.class );
 
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
 
         final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean( searchRequest );
         final RestResultBean restResultBean = RestResultBean.withData( searchResultBean );
@@ -166,12 +159,13 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile );
 
         final UserIdentity userIdentity;
         {
             final String userKey = pwmRequest.readParameterAsString( PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation );
-            if ( userKey == null || userKey.isEmpty() )
+            if ( StringUtil.isEmpty( userKey ) )
             {
                 userIdentity = pwmRequest.getUserInfoIfLoggedIn();
                 if ( userIdentity == null )
@@ -194,7 +188,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
 
         try
         {
-            final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+            final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
             final OrgChartDataBean orgChartData = peopleSearchDataReader.makeOrgChartData( userIdentity, noChildren );
 
             addExpiresHeadersToResponse( pwmRequest );
@@ -219,7 +213,8 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
         final String userKey = pwmRequest.readParameterAsString( PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation );
         final UserIdentity userIdentity = readUserIdentityFromKey( pwmRequest, userKey );
 
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
         final UserDetailBean detailData = peopleSearchDataReader.makeUserDetailRequest( userIdentity );
 
         addExpiresHeadersToResponse( pwmRequest );
@@ -242,45 +237,25 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
         }
 
-
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
         final UserIdentity userIdentity = readUserIdentityFromKey( pwmRequest, userKey );
 
         LOGGER.debug( pwmRequest, () -> "received user photo request to view user " + userIdentity.toString() );
 
-        final PhotoDataBean photoData;
-        try
-        {
-            photoData = peopleSearchDataReader.readPhotoDataFromLdap( userIdentity );
-        }
-        catch ( PwmOperationalException e )
-        {
-            final ErrorInformation errorInformation = e.getErrorInformation();
-            LOGGER.error( pwmRequest, errorInformation );
-            pwmRequest.respondWithError( errorInformation, false );
-            return ProcessStatus.Halt;
-        }
-
-        addExpiresHeadersToResponse( pwmRequest );
-
-        try ( OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream() )
-        {
-            final HttpServletResponse resp = pwmRequest.getPwmResponse().getHttpServletResponse();
-            resp.setContentType( photoData.getMimeType() );
+        final Callable<Optional<PhotoDataBean>> callablePhotoReader = () -> peopleSearchDataReader.readPhotoData( userIdentity );
 
-            if ( photoData.getContents() != null )
-            {
-                outputStream.write( photoData.getContents().copyOf() );
-            }
-        }
+        PhotoDataReader.servletRespondWithPhoto( pwmRequest, callablePhotoReader );
         return ProcessStatus.Halt;
     }
 
     private void addExpiresHeadersToResponse( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        final long maxCacheSeconds = pwmRequest.getConfig().readSettingAsLong( PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS );
-        pwmRequest.getPwmResponse().getHttpServletResponse().setDateHeader( HttpHeader.Expires.getHttpName(), System.currentTimeMillis() + ( maxCacheSeconds * 1000L ) );
-        pwmRequest.getPwmResponse().setHeader( HttpHeader.CacheControl,  "private, max-age=" + maxCacheSeconds );
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile( pwmRequest ) );
+        final TimeDuration maxCacheTime = peopleSearchConfiguration.getMaxCacheTime();
+        pwmRequest.getPwmResponse().getHttpServletResponse().setDateHeader( HttpHeader.Expires.getHttpName(), System.currentTimeMillis() + ( maxCacheTime.asMillis() ) );
+        pwmRequest.getPwmResponse().setHeader( HttpHeader.CacheControl,  "private, max-age=" + maxCacheTime.as( TimeDuration.Unit.SECONDS ) );
     }
 
     @ActionHandler( action = "exportOrgChart" )
@@ -290,9 +265,10 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
         final String userKey = pwmRequest.readParameterAsString( PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation );
         final int requestedDepth = pwmRequest.readParameterAsInt( PARAM_DEPTH, 1 );
         final UserIdentity userIdentity = readUserIdentityFromKey( pwmRequest, userKey );
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
 
-        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile( pwmRequest ) );
         final PeopleSearchClientConfigBean peopleSearchClientConfigBean = PeopleSearchClientConfigBean.fromConfig( pwmRequest, peopleSearchConfiguration, userIdentity );
 
         if ( !peopleSearchClientConfigBean.isEnableExport() )
@@ -321,9 +297,10 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
         final String userKey = pwmRequest.readParameterAsString( PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation );
         final int requestedDepth = pwmRequest.readParameterAsInt( PARAM_DEPTH, 1 );
         final UserIdentity userIdentity = readUserIdentityFromKey( pwmRequest, userKey );
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
 
-        final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
+        final PeopleSearchConfiguration peopleSearchConfiguration = new PeopleSearchConfiguration( pwmRequest.getConfig(), peopleSearchProfile );
 
         if ( !peopleSearchConfiguration.isEnableMailtoLinks() )
         {
@@ -339,6 +316,27 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
         return ProcessStatus.Halt;
     }
 
+    static PeopleSearchProfile peopleSearchProfile( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.getURL().isPublicUrl() )
+        {
+            final Optional<PeopleSearchProfile> profile = pwmRequest.getConfig().getPublicPeopleSearchProfile();
+            if ( !profile.isPresent() )
+            {
+                throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "public peoplesearch profile not assigned" );
+            }
+            return profile.get();
+        }
+
+        if ( pwmRequest.isAuthenticated() )
+        {
+            return pwmRequest.getPwmSession().getSessionManager().getPeopleSearchProfile( pwmRequest.getPwmApplication() );
+        }
+
+        throw PwmUnrecoverableException.newException( PwmError.ERROR_NO_PROFILE_ASSIGNED, "unable to load peoplesearch profile for authenticated user" );
+    }
+
 
     static UserIdentity readUserIdentityFromKey( final PwmRequest pwmRequest, final String userKey )
             throws PwmUnrecoverableException
@@ -350,7 +348,8 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
             throw new PwmUnrecoverableException( errorInformation );
         }
 
-        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
+        final PeopleSearchProfile peopleSearchProfile = peopleSearchProfile( pwmRequest );
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest, peopleSearchProfile );
         final UserIdentity userIdentity = UserIdentity.fromKey( userKey, pwmRequest.getPwmApplication() );
         peopleSearchDataReader.checkIfUserIdentityViewable( userIdentity );
         return userIdentity;

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

@@ -0,0 +1,312 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2019 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.http.servlet.peoplesearch;
+
+import com.novell.ldapchai.provider.ChaiProvider;
+import lombok.Builder;
+import lombok.Value;
+import password.pwm.AppProperty;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpHeader;
+import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmURL;
+import password.pwm.http.bean.ImmutableByteArray;
+import password.pwm.ldap.LdapOperationsHelper;
+import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.ldap.PhotoDataBean;
+import password.pwm.svc.httpclient.PwmHttpClient;
+import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
+import password.pwm.svc.httpclient.PwmHttpClientRequest;
+import password.pwm.svc.httpclient.PwmHttpClientResponse;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStream;
+import java.time.Instant;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+
+public class PhotoDataReader
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PhotoDataReader.class );
+
+    private final Settings settings;
+    private final PwmRequest pwmRequest;
+    private final UserIdentity userIdentity;
+
+    @Value
+    @Builder
+    public static class Settings
+    {
+        private boolean enabled;
+        private List<UserPermission> photoPermissions;
+        private ChaiProvider chaiProvider;
+    }
+
+    public enum PhotoReaderMethod
+    {
+        Ldap,
+        ServerHttp,
+        ClientHttp,
+    }
+
+    public PhotoDataReader( final PwmRequest pwmRequest, final Settings settings, final UserIdentity userIdentity )
+    {
+        this.pwmRequest = pwmRequest;
+        this.settings = settings;
+        this.userIdentity = userIdentity;
+    }
+
+    private PhotoReaderMethod figurePhotoDataReaderMethod()
+            throws PwmUnrecoverableException
+    {
+        final Optional<String> photoUrlOverride = getPhotoUrlOverride( userIdentity );
+        if ( !photoUrlOverride.isPresent() )
+        {
+            return PhotoReaderMethod.Ldap;
+        }
+
+        final boolean enableInternalHttpProxy = Boolean.parseBoolean( pwmRequest.getConfig().readAppProperty( AppProperty.PHOTO_INTERNAL_HTTP_PROXY_ENABLE ) );
+        if ( enableInternalHttpProxy )
+        {
+            return PhotoReaderMethod.ServerHttp;
+        }
+
+        return PhotoReaderMethod.ClientHttp;
+    }
+
+    private boolean verifyViewPhotoPermission()
+            throws PwmUnrecoverableException
+    {
+        final Instant startTime = Instant.now();
+
+        if ( !settings.isEnabled() )
+        {
+            return false;
+        }
+
+        final List<UserPermission> permissions = settings.getPhotoPermissions();
+        if ( JavaHelper.isEmpty( permissions ) )
+        {
+            return true;
+        }
+
+        final boolean hasPermission = LdapPermissionTester.testUserPermissions( pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userIdentity, permissions );
+        if ( !hasPermission )
+        {
+            LOGGER.debug( pwmRequest, () -> "user " + userIdentity + " failed photo query filter, denying photo view ("
+                    + TimeDuration.compactFromCurrent( startTime ) + ")" );
+        }
+
+        return hasPermission;
+    }
+
+    public String figurePhotoURL()
+            throws PwmUnrecoverableException
+    {
+        if ( !verifyViewPhotoPermission() )
+        {
+            return null;
+        }
+
+        final PhotoReaderMethod method = figurePhotoDataReaderMethod( );
+
+        switch ( method )
+        {
+            case ClientHttp:
+                return getPhotoUrlOverride( userIdentity ).orElse( null );
+
+            case Ldap:
+            case ServerHttp:
+                String returnUrl = pwmRequest.getURLwithoutQueryString();
+                returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_ACTION_REQUEST, PeopleSearchServlet.PeopleSearchActions.photo.name() );
+                returnUrl = PwmURL.appendAndEncodeUrlParameters( returnUrl, PwmConstants.PARAM_USERKEY,  userIdentity.toObfuscatedKey( pwmRequest.getPwmApplication() ) );
+                return returnUrl;
+
+            default:
+                JavaHelper.unhandledSwitchStatement( method );
+
+        }
+
+        return null;
+    }
+
+    public Optional<PhotoDataBean> readPhotoData( )
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        final Instant startTime = Instant.now();
+
+        if ( !verifyViewPhotoPermission() )
+        {
+            return Optional.empty();
+        }
+
+        final PhotoReaderMethod method = figurePhotoDataReaderMethod( );
+
+        Optional<PhotoDataBean> photoDataBean = Optional.empty();
+        try
+        {
+            switch ( method )
+            {
+                case Ldap:
+                    photoDataBean = readPhotoDataFromLdap();
+                    break;
+
+                case ServerHttp:
+                    photoDataBean = readPhotoDataFromHTTP();
+                    break;
+
+                default:
+                    JavaHelper.unhandledSwitchStatement( method );
+            }
+        }
+        finally
+        {
+            final Optional<PhotoDataBean> finalData = photoDataBean;
+            if ( finalData.isPresent() )
+            {
+                LOGGER.trace( pwmRequest, () -> "user photo data received for " + userIdentity.toDisplayString()
+                        + " " + finalData.get().toString()
+                        + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+            }
+            else
+            {
+                LOGGER.trace( pwmRequest, () -> "no user photo data received for " + userIdentity.toDisplayString()
+                        + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+            }
+        }
+
+        return photoDataBean;
+    }
+
+    private Optional<PhotoDataBean> readPhotoDataFromLdap()
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        return LdapOperationsHelper.readPhotoDataFromLdap(
+                pwmRequest.getConfig(),
+                pwmRequest.getPwmApplication().getProxiedChaiUser( userIdentity ).getChaiProvider(),
+                userIdentity
+        );
+    }
+
+    private Optional<PhotoDataBean> readPhotoDataFromHTTP()
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        final Optional<String> overrideURL = getPhotoUrlOverride( userIdentity );
+        if ( !overrideURL.isPresent() )
+        {
+            return Optional.empty();
+        }
+
+        try
+        {
+            final PwmHttpClientConfiguration configuration = PwmHttpClientConfiguration.builder()
+                    .trustManagerType( PwmHttpClientConfiguration.TrustManagerType.promiscuous )
+                    .build();
+            final PwmHttpClient pwmHttpClient = pwmRequest.getPwmApplication().getHttpClientService().getPwmHttpClient( configuration );
+            final PwmHttpClientRequest clientRequest = PwmHttpClientRequest.builder()
+                    .method( HttpMethod.GET )
+                    .url( overrideURL.get() )
+                    .build();
+            final PwmHttpClientResponse response = pwmHttpClient.makeRequest( clientRequest, pwmRequest.getSessionLabel() );
+            if ( response != null )
+            {
+                final ImmutableByteArray bodyContents = response.getBinaryBody();
+                if ( bodyContents != null && !bodyContents.isEmpty() )
+                {
+                    final String mimeType = response.getContentType().getMimeType();
+                    return Optional.of( new PhotoDataBean( mimeType, bodyContents ) );
+                }
+            }
+            return Optional.empty();
+        }
+        catch ( Exception e )
+        {
+            final String msg = "error reading remote http photo data: " + JavaHelper.readHostileExceptionMessage( e );
+            throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_SERVICE_UNREACHABLE, msg ) );
+        }
+    }
+
+    private Optional<String> getPhotoUrlOverride( final UserIdentity userIdentity )
+            throws PwmUnrecoverableException
+    {
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmRequest.getConfig() );
+        final String configuredUrl = ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_PHOTO_URL_OVERRIDE );
+
+        if ( !StringUtil.isEmpty( configuredUrl ) )
+        {
+            final MacroMachine macroMachine = MacroMachine.forUser( pwmRequest.commonValues(), userIdentity );
+            return Optional.of( macroMachine.expandMacros( configuredUrl ) );
+
+        }
+
+        return Optional.empty();
+    }
+
+    public static void servletRespondWithPhoto(
+            final PwmRequest pwmRequest,
+            final Callable<Optional<PhotoDataBean>> photoReader
+    )
+    {
+        final long cacheSeconds = JavaHelper.silentParseLong( pwmRequest.getConfig().readAppProperty( AppProperty.PHOTO_CLIENT_CACHE_SECONDS ), 3600 );
+        final TimeDuration maxCacheTime = TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS );
+        pwmRequest.getPwmResponse().getHttpServletResponse().setDateHeader( HttpHeader.Expires.getHttpName(), System.currentTimeMillis() + ( maxCacheTime.asMillis() ) );
+        pwmRequest.getPwmResponse().setHeader( HttpHeader.CacheControl,  "private, max-age=" + maxCacheTime.as( TimeDuration.Unit.SECONDS ) );
+
+        try ( OutputStream outputStream = pwmRequest.getPwmResponse().getOutputStream() )
+        {
+            final Optional<PhotoDataBean> optionalPhotoDataBean = photoReader.call();
+            if ( optionalPhotoDataBean.isPresent() )
+            {
+                final PhotoDataBean photoDataBean = optionalPhotoDataBean.get();
+                final HttpServletResponse resp = pwmRequest.getPwmResponse().getHttpServletResponse();
+                resp.setContentType( photoDataBean.getMimeType() );
+
+                if ( photoDataBean.getContents() != null && !photoDataBean.getContents().isEmpty() )
+                {
+                    outputStream.write( photoDataBean.getContents().copyOf() );
+                }
+            }
+        }
+        catch ( Exception e )
+        {
+            LOGGER.debug( pwmRequest, () -> "error reading user photo data: " + e.getMessage() );
+            if ( !pwmRequest.getPwmResponse().isCommitted() )
+            {
+                pwmRequest.getPwmResponse().setStatus( 500 );
+            }
+        }
+    }
+}

+ 2 - 9
server/src/main/java/password/pwm/http/servlet/peoplesearch/PrivatePeopleSearchServlet.java

@@ -21,10 +21,7 @@
 package password.pwm.http.servlet.peoplesearch;
 
 
-import password.pwm.Permission;
 import password.pwm.PwmConstants;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
@@ -49,11 +46,7 @@ public class PrivatePeopleSearchServlet extends PeopleSearchServlet
     @Override
     public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
     {
-        if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmApplication(), Permission.PEOPLE_SEARCH ) )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_UNAUTHORIZED ) );
-        }
-
-        return super.preProcessCheck( pwmRequest );
+        PeopleSearchServlet.peopleSearchProfile( pwmRequest );
+        return ProcessStatus.Continue;
     }
 }

+ 2 - 12
server/src/main/java/password/pwm/http/servlet/peoplesearch/PublicPeopleSearchServlet.java

@@ -21,9 +21,6 @@
 package password.pwm.http.servlet.peoplesearch;
 
 import password.pwm.PwmConstants;
-import password.pwm.config.PwmSetting;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
@@ -48,14 +45,7 @@ public class PublicPeopleSearchServlet extends PeopleSearchServlet
     @Override
     public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
     {
-        if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC ) )
-        {
-            throw new PwmUnrecoverableException( new ErrorInformation(
-                    PwmError.ERROR_SERVICE_NOT_AVAILABLE,
-                    "public peoplesearch service is not enabled" )
-            );
-        }
-
-        return super.preProcessCheck( pwmRequest );
+        PeopleSearchServlet.peopleSearchProfile( pwmRequest );
+        return ProcessStatus.Continue;
     }
 }

+ 2 - 8
server/src/main/java/password/pwm/http/servlet/peoplesearch/bean/PeopleSearchClientConfigBean.java

@@ -22,10 +22,7 @@ package password.pwm.http.servlet.peoplesearch.bean;
 
 import lombok.Builder;
 import lombok.Value;
-import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.Configuration;
-import password.pwm.config.PwmSetting;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
@@ -63,26 +60,23 @@ public class PeopleSearchClientConfigBean implements Serializable
     )
             throws PwmUnrecoverableException
     {
-        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
-        final Configuration configuration = pwmApplication.getConfig();
         final Locale locale = pwmRequest.getLocale();
 
         final Map<String, String> searchColumns = new LinkedHashMap<>();
-        final List<FormConfiguration> searchForm = configuration.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
+        final List<FormConfiguration> searchForm = peopleSearchConfiguration.getSearchResultForm();
         for ( final FormConfiguration formConfiguration : searchForm )
         {
             searchColumns.put( formConfiguration.getName(),
                     formConfiguration.getLabel( locale ) );
         }
 
-
         final List<SearchAttributeBean> searchAttributeBeans = SearchAttributeBean.searchAttributesFromForm(
                 locale,
                 peopleSearchConfiguration.getSearchForm() );
 
         return PeopleSearchClientConfigBean.builder()
                 .searchColumns( searchColumns )
-                .enablePhoto( peopleSearchConfiguration.isPhotosEnabled( userIdentity, pwmRequest.getSessionLabel() ) )
+                .enablePhoto( peopleSearchConfiguration.isPhotosEnabled() )
                 .orgChartEnabled( peopleSearchConfiguration.isOrgChartEnabled() )
                 .orgChartShowChildCount( peopleSearchConfiguration.isOrgChartShowChildCount() )
                 .orgChartMaxParents( peopleSearchConfiguration.getOrgChartMaxParents() )

+ 3 - 1
server/src/main/java/password/pwm/http/servlet/resource/ResourceFileRequest.java

@@ -51,7 +51,9 @@ class ResourceFileRequest
     private static final PwmLogger LOGGER = PwmLogger.forClass( ResourceFileRequest.class );
 
     private static final Map<String, String> WEB_JAR_VERSION_MAP = Collections.unmodifiableMap( new HashMap<>( new WebJarAssetLocator().getWebJars() ) );
-    private static final Collection<String> WEB_JAR_ASSET_LIST = Collections.unmodifiableCollection( new ArrayList<>( new WebJarAssetLocator().getFullPathIndex().values() ) );
+
+    /** Contains a list of all resources (files) found inside the resources folder of all JARs in the WAR's classpath. **/
+    private static final Collection<String> WEB_JAR_ASSET_LIST = Collections.unmodifiableCollection( new ArrayList<>( new WebJarAssetLocator().listAssets() ) );
 
     private final HttpServletRequest httpServletRequest;
     private final Configuration configuration;

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

@@ -30,10 +30,10 @@ import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.servlet.PwmServlet;
-import password.pwm.util.EventRateMeter;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.MovingAverage;
 import password.pwm.util.logging.PwmLogger;
 
 import javax.servlet.ServletException;
@@ -211,7 +211,7 @@ public class ResourceFileServlet extends HttpServlet implements PwmServlet
 
             pwmRequest.debugHttpRequestToLog( debugText );
 
-            final EventRateMeter.MovingAverage cacheHitRatio = resourceService.getCacheHitRatio();
+            final MovingAverage cacheHitRatio = resourceService.getCacheHitRatio();
             StatisticsManager.incrementStat( pwmApplication, Statistic.HTTP_RESOURCE_REQUESTS );
             cacheHitRatio.update( fromCache ? 1 : 0 );
         }

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/resource/ResourceServletService.java

@@ -31,9 +31,9 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.svc.PwmService;
-import password.pwm.util.EventRateMeter;
 import password.pwm.util.java.FileSystemUtility;
 import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.MovingAverage;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -59,7 +59,7 @@ public class ResourceServletService implements PwmService
 
     private ResourceServletConfiguration resourceServletConfiguration;
     private Cache<CacheKey, CacheEntry> cache;
-    private EventRateMeter.MovingAverage cacheHitRatio = new EventRateMeter.MovingAverage( 60 * 60 * 1000 );
+    private MovingAverage cacheHitRatio = new MovingAverage( 60 * 60 * 1000 );
     private String resourceNonce;
     private STATUS status = STATUS.NEW;
 
@@ -75,7 +75,7 @@ public class ResourceServletService implements PwmService
         return cache;
     }
 
-    public EventRateMeter.MovingAverage getCacheHitRatio( )
+    public MovingAverage getCacheHitRatio( )
     {
         return cacheHitRatio;
     }

+ 237 - 77
server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java

@@ -20,13 +20,13 @@
 
 package password.pwm.http.tag;
 
+import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.config.Configuration;
 import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordRule;
-import password.pwm.util.password.PasswordRuleReaderHelper;
 import password.pwm.error.PwmException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
@@ -37,6 +37,7 @@ import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
+import password.pwm.util.password.PasswordRuleReaderHelper;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -44,6 +45,8 @@ import javax.servlet.jsp.JspTagException;
 import javax.servlet.jsp.tagext.TagSupport;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.MissingResourceException;
@@ -58,31 +61,84 @@ public class PasswordRequirementsTag extends TagSupport
     private String prepend;
     private String form;
 
-    @SuppressWarnings( "checkstyle:MethodLength" )
     public static List<String> getPasswordRequirementsStrings(
-            final PwmPasswordPolicy pwordPolicy,
+            final PwmPasswordPolicy passwordPolicy,
             final Configuration config,
             final Locale locale,
             final MacroMachine macroMachine
     )
     {
-        final List<String> returnValues = new ArrayList<>();
-        final ADPolicyComplexity adPolicyLevel = pwordPolicy.getRuleHelper().getADComplexityLevel();
+        final List<String> ruleTexts = new ArrayList<>(  );
+        final PolicyValues policyValues = new PolicyValues( passwordPolicy, passwordPolicy.getRuleHelper(), locale, config, macroMachine );
+        for ( final RuleTextGenerator ruleTextGenerator : RULE_TEXT_GENERATORS )
+        {
+            ruleTexts.addAll( ruleTextGenerator.generate( policyValues ) );
+        }
 
+        return Collections.unmodifiableList( ruleTexts );
+    }
 
-        final PasswordRuleReaderHelper ruleHelper = pwordPolicy.getRuleHelper();
+    private static final List<RuleTextGenerator> RULE_TEXT_GENERATORS = Collections.unmodifiableList( Arrays.asList(
+            new CaseSensitiveRuleTextGenerator(),
+            new MinLengthRuleTextGenerator(),
+            new MaxLengthRuleTextGenerator(),
+            new MinAlphaRuleTextGenerator(),
+            new MaxAlphaRuleTextGenerator(),
+            new NumericCharsRuleTextGenerator(),
+            new SpecialCharsRuleTextGenerator(),
+            new MaximumRepeatRuleTextGenerator(),
+            new MaximumSequentialRepeatRuleTextGenerator(),
+            new MinimumLowerRuleTextGenerator(),
+            new MaximumLowerRuleTextGenerator(),
+            new MinimumUpperRuleTextGenerator(),
+            new MaximumUpperRuleTextGenerator(),
+            new MinimumUniqueRuleTextGenerator(),
+            new DisallowedValuesRuleTextGenerator(),
+            new WordlistRuleTextGenerator(),
+            new DisallowedAttributesRuleTextGenerator(),
+            new MaximumOldCharsRuleTextGenerator(),
+            new MinimumLifetimeRuleTextGenerator(),
+            new ADRuleTextGenerator(),
+            new UniqueRequiredRuleTextGenerator()
+    ) );
+
+    private interface RuleTextGenerator
+    {
+        List<String> generate( PolicyValues policyValues );
+    }
 
-        if ( ruleHelper.readBooleanValue( PwmPasswordRule.CaseSensitive ) )
-        {
-            returnValues.add( getLocalString( Message.Requirement_CaseSensitive, null, locale, config ) );
-        }
-        else
+    @Value
+    private static class PolicyValues
+    {
+        private PwmPasswordPolicy passwordPolicy;
+        private PasswordRuleReaderHelper ruleHelper;
+        private Locale locale;
+        private Configuration config;
+        private MacroMachine macroMachine;
+    }
+
+    private static class CaseSensitiveRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            returnValues.add( getLocalString( Message.Requirement_NotCaseSensitive, null, locale, config ) );
+            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.CaseSensitive ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_CaseSensitive, null, policyValues ) );
+            }
+            else
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_NotCaseSensitive, null, policyValues ) );
+            }
         }
+    }
 
+    private static class MinLengthRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumLength );
+            int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLength );
+            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
+
             if ( adPolicyLevel == ADPolicyComplexity.AD2003 || adPolicyLevel == ADPolicyComplexity.AD2008 )
             {
                 if ( value < 6 )
@@ -92,154 +148,223 @@ public class PasswordRequirementsTag extends TagSupport
             }
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MinLength, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MinLength, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaxLengthRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MaximumLength );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLength );
             if ( value > 0 && value < 64 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxLength, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxLength, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MinAlphaRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumAlpha );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumAlpha );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MinAlpha, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MinAlpha, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaxAlphaRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MaximumAlpha );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumAlpha );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxAlpha, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxAlpha, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class NumericCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
+            final PasswordRuleReaderHelper ruleHelper = policyValues.getRuleHelper();
             if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowNumeric ) )
             {
-                returnValues.add( getLocalString( Message.Requirement_AllowNumeric, null, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_AllowNumeric, null, policyValues ) );
             }
             else
             {
+                final List<String> returnValues = new ArrayList<>(  );
                 final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumNumeric );
                 if ( minValue > 0 )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_MinNumeric, minValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_MinNumeric, minValue, policyValues ) );
                 }
 
                 final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumNumeric );
                 if ( maxValue > 0 )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_MaxNumeric, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_MaxNumeric, maxValue, policyValues ) );
                 }
 
                 if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharNumeric ) )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_FirstNumeric, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_FirstNumeric, maxValue, policyValues ) );
                 }
 
                 if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharNumeric ) )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_LastNumeric, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_LastNumeric, maxValue, policyValues ) );
                 }
+                return returnValues;
             }
         }
+    }
 
+    private static class SpecialCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
+            final PasswordRuleReaderHelper ruleHelper = policyValues.getRuleHelper();
             if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowSpecial ) )
             {
-                returnValues.add( getLocalString( Message.Requirement_AllowSpecial, null, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_AllowSpecial, null, policyValues ) );
             }
             else
             {
+                final List<String> returnValues = new ArrayList<>(  );
                 final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumSpecial );
                 if ( minValue > 0 )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_MinSpecial, minValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_MinSpecial, minValue, policyValues ) );
                 }
 
                 final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumSpecial );
                 if ( maxValue > 0 )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_MaxSpecial, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_MaxSpecial, maxValue, policyValues ) );
                 }
 
                 if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharSpecial ) )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_FirstSpecial, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_FirstSpecial, maxValue, policyValues ) );
                 }
 
                 if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharSpecial ) )
                 {
-                    returnValues.add( getLocalString( Message.Requirement_LastSpecial, maxValue, locale, config ) );
+                    returnValues.add( getLocalString( Message.Requirement_LastSpecial, maxValue, policyValues ) );
                 }
+                return returnValues;
             }
         }
+    }
 
+    private static class MaximumRepeatRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = pwordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MaximumRepeat );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumRepeat );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxRepeat, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxRepeat, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaximumSequentialRepeatRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = pwordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MaximumSequentialRepeat );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumSequentialRepeat );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxSeqRepeat, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxSeqRepeat, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MinimumLowerRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumLowerCase );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLowerCase );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MinLower, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MinLower, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaximumLowerRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MaximumLowerCase );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLowerCase );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxLower, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxLower, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MinimumUpperRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumUpperCase );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumUpperCase );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MinUpper, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MinUpper, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaximumUpperRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MaximumUpperCase );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumUpperCase );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MaxUpper, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxUpper, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MinimumUniqueRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumUnique );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumUnique );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_MinUnique, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_MinUnique, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class DisallowedValuesRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final List<String> setValue = ruleHelper.getDisallowedValues();
+            final List<String> setValue = policyValues.getRuleHelper().getDisallowedValues();
             if ( !setValue.isEmpty() )
             {
                 final StringBuilder fieldValue = new StringBuilder();
@@ -247,37 +372,59 @@ public class PasswordRequirementsTag extends TagSupport
                 {
                     fieldValue.append( " " );
 
-                    final String expandedValue = macroMachine.expandMacros( loopValue );
+                    final String expandedValue = policyValues.getMacroMachine().expandMacros( loopValue );
                     fieldValue.append( StringUtil.escapeHtml( expandedValue ) );
                 }
-                returnValues.add(
-                        getLocalString( Message.Requirement_DisAllowedValues, fieldValue.toString(), locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_DisAllowedValues, fieldValue.toString(), policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class WordlistRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final List<String> setValue = ruleHelper.getDisallowedAttributes();
-            if ( !setValue.isEmpty() || adPolicyLevel == ADPolicyComplexity.AD2003 )
+            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.EnableWordlist ) )
             {
-                returnValues.add( getLocalString( Message.Requirement_DisAllowedAttributes, "", locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_WordList, "", policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
-        if ( ruleHelper.readBooleanValue( PwmPasswordRule.EnableWordlist ) )
+    private static class DisallowedAttributesRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            returnValues.add( getLocalString( Message.Requirement_WordList, "", locale, config ) );
+            final List<String> setValue = policyValues.getRuleHelper().getDisallowedAttributes();
+            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
+            if ( !setValue.isEmpty() || adPolicyLevel == ADPolicyComplexity.AD2003 )
+            {
+                return Collections.singletonList(  getLocalString( Message.Requirement_DisAllowedAttributes, "", policyValues ) );
+            }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MaximumOldCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MaximumOldChars );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumOldChars );
             if ( value > 0 )
             {
-                returnValues.add( getLocalString( Message.Requirement_OldChar, value, locale, config ) );
+                return Collections.singletonList( getLocalString( Message.Requirement_OldChar, value, policyValues ) );
             }
+            return Collections.emptyList();
         }
+    }
 
+    private static class MinimumLifetimeRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int value = ruleHelper.readIntValue( PwmPasswordRule.MinimumLifetime );
+            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
             if ( value > 0 )
             {
                 final int secondsPerDay = 60 * 60 * 24;
@@ -287,41 +434,54 @@ public class PasswordRequirementsTag extends TagSupport
                 {
                     final int valueAsDays = value / ( 60 * 60 * 24 );
                     final Display key = valueAsDays <= 1 ? Display.Display_Day : Display.Display_Days;
-                    durationStr = valueAsDays + " " + LocaleHelper.getLocalizedMessage( locale, key, config );
+                    durationStr = valueAsDays + " " + LocaleHelper.getLocalizedMessage( policyValues.getLocale(), key, policyValues.getConfig() );
                 }
                 else
                 {
                     final int valueAsHours = value / ( 60 * 60 );
                     final Display key = valueAsHours <= 1 ? Display.Display_Hour : Display.Display_Hours;
-                    durationStr = valueAsHours + " " + LocaleHelper.getLocalizedMessage( locale, key, config );
+                    durationStr = valueAsHours + " " + LocaleHelper.getLocalizedMessage( policyValues.getLocale(), key, policyValues.getConfig() );
                 }
 
-                final String userMsg = Message.getLocalizedMessage( locale, Message.Requirement_MinimumFrequency, config,
-                        durationStr );
-                returnValues.add( userMsg );
+                final String userMsg = Message.getLocalizedMessage( policyValues.getLocale(), Message.Requirement_MinimumFrequency, policyValues.getConfig(), durationStr );
+                return Collections.singletonList( userMsg );
             }
+            return Collections.emptyList();
         }
+    }
 
-        if ( adPolicyLevel == ADPolicyComplexity.AD2003 )
-        {
-            returnValues.add( getLocalString( Message.Requirement_ADComplexity, "", locale, config ) );
-        }
-        else if ( adPolicyLevel == ADPolicyComplexity.AD2008 )
+    private static class ADRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            final int maxViolations = ruleHelper.readIntValue( PwmPasswordRule.ADComplexityMaxViolations );
-            final int minGroups = 5 - maxViolations;
-            returnValues.add( getLocalString( Message.Requirement_ADComplexity2008, String.valueOf( minGroups ), locale, config ) );
+            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
+            if ( adPolicyLevel == ADPolicyComplexity.AD2003 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity, "", policyValues ) );
+            }
+            else if ( adPolicyLevel == ADPolicyComplexity.AD2008 )
+            {
+                final int maxViolations = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.ADComplexityMaxViolations );
+                final int minGroups = 5 - maxViolations;
+                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity2008, String.valueOf( minGroups ), policyValues ) );
+            }
+            return Collections.emptyList();
         }
+    }
 
-        if ( ruleHelper.readBooleanValue( PwmPasswordRule.UniqueRequired ) )
+    private static class UniqueRequiredRuleTextGenerator implements RuleTextGenerator
+    {
+        public List<String> generate( final PolicyValues policyValues )
         {
-            returnValues.add( getLocalString( Message.Requirement_UniqueRequired, "", locale, config ) );
+            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.UniqueRequired ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_UniqueRequired, "", policyValues ) );
+            }
+            return Collections.emptyList();
         }
-
-        return returnValues;
     }
 
-    private static String getLocalString( final Message message, final int size, final Locale locale, final Configuration config )
+    private static String getLocalString( final Message message, final int size, final PolicyValues policyValues )
     {
         final Message effectiveMessage = size > 1 && message.getPluralMessage() != null
                 ? message.getPluralMessage()
@@ -329,7 +489,7 @@ public class PasswordRequirementsTag extends TagSupport
 
         try
         {
-            return Message.getLocalizedMessage( locale, effectiveMessage, config, String.valueOf( size ) );
+            return Message.getLocalizedMessage( policyValues.getLocale(), effectiveMessage, policyValues.getConfig(), String.valueOf( size ) );
         }
         catch ( MissingResourceException e )
         {
@@ -338,11 +498,11 @@ public class PasswordRequirementsTag extends TagSupport
         return "UNKNOWN MESSAGE STRING";
     }
 
-    private static String getLocalString( final Message message, final String field, final Locale locale, final Configuration config )
+    private static String getLocalString( final Message message, final String field, final PolicyValues policyValues )
     {
         try
         {
-            return Message.getLocalizedMessage( locale, message, config, field );
+            return Message.getLocalizedMessage( policyValues.getLocale(), message, policyValues.getConfig(), field );
         }
         catch ( MissingResourceException e )
         {

+ 23 - 15
server/src/main/java/password/pwm/http/tag/conditional/PwmIfTest.java

@@ -28,19 +28,20 @@ import password.pwm.PwmConstants;
 import password.pwm.PwmEnvironment;
 import password.pwm.bean.PasswordStatus;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.profile.ProfileType;
+import password.pwm.config.profile.PeopleSearchProfile;
+import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.SetupOtpProfile;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMonitor;
 import password.pwm.health.HealthStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
-import password.pwm.http.servlet.peoplesearch.PeopleSearchConfiguration;
 import password.pwm.ldap.UserInfo;
 import password.pwm.svc.PwmService;
 import password.pwm.util.java.StringUtil;
 
 import java.util.Arrays;
+import java.util.Optional;
 
 public enum PwmIfTest
 {
@@ -63,7 +64,7 @@ public enum PwmIfTest
     hasCustomJavascript( new HasCustomJavascript() ),
     setupChallengeEnabled( new BooleanPwmSettingTest( PwmSetting.CHALLENGE_ENABLE ) ),
     shortcutsEnabled( new BooleanPwmSettingTest( PwmSetting.SHORTCUT_ENABLE ) ),
-    peopleSearchEnabled( new BooleanPwmSettingTest( PwmSetting.PEOPLE_SEARCH_ENABLE ) ),
+    peopleSearchAvailable(  new BooleanPwmSettingTest( PwmSetting.PEOPLE_SEARCH_ENABLE ), new ActorHasProfileTest( ProfileDefinition.PeopleSearch )  ),
     orgChartEnabled( new OrgChartEnabled() ),
     passwordExpired( new PasswordExpired() ),
     showMaskedTokenSelection( new BooleanAppPropertyTest( AppProperty.TOKEN_MASK_SHOW_SELECTION ) ),
@@ -76,9 +77,9 @@ public enum PwmIfTest
     activateUserEnabled( new BooleanPwmSettingTest( PwmSetting.ACTIVATE_USER_ENABLE ) ),
     newUserRegistrationEnabled( new BooleanPwmSettingTest( PwmSetting.NEWUSER_ENABLE ) ),
 
-    updateProfileAvailable( new BooleanPwmSettingTest( PwmSetting.UPDATE_PROFILE_ENABLE ), new ActorHasProfileTest( ProfileType.UpdateAttributes ) ),
-    helpdeskAvailable( new BooleanPwmSettingTest( PwmSetting.HELPDESK_ENABLE ), new ActorHasProfileTest( ProfileType.Helpdesk ) ),
-    DeleteAccountAvailable( new BooleanPwmSettingTest( PwmSetting.DELETE_ACCOUNT_ENABLE ), new ActorHasProfileTest( ProfileType.DeleteAccount ) ),
+    updateProfileAvailable( new BooleanPwmSettingTest( PwmSetting.UPDATE_PROFILE_ENABLE ), new ActorHasProfileTest( ProfileDefinition.UpdateAttributes ) ),
+    helpdeskAvailable( new BooleanPwmSettingTest( PwmSetting.HELPDESK_ENABLE ), new ActorHasProfileTest( ProfileDefinition.Helpdesk ) ),
+    DeleteAccountAvailable( new BooleanPwmSettingTest( PwmSetting.DELETE_ACCOUNT_ENABLE ), new ActorHasProfileTest( ProfileDefinition.DeleteAccount ) ),
     guestRegistrationAvailable( new BooleanPwmSettingTest( PwmSetting.GUEST_ENABLE ), new BooleanPermissionTest( Permission.GUEST_REGISTRATION ) ),
 
     booleanSetting( new BooleanPwmSettingTest( null ) ),
@@ -239,8 +240,8 @@ public enum PwmIfTest
 
             return pwmRequest != null
                     && pwmRequest.getPwmSession().getSessionManager().checkPermission(
-                            pwmRequest.getPwmApplication(),
-                            permission );
+                    pwmRequest.getPwmApplication(),
+                    permission );
         }
     }
 
@@ -413,17 +414,17 @@ public enum PwmIfTest
     private static class ActorHasProfileTest implements Test
     {
 
-        private final ProfileType profileType;
+        private final ProfileDefinition profileDefinition;
 
-        ActorHasProfileTest( final ProfileType profileType )
+        ActorHasProfileTest( final ProfileDefinition profileDefinition )
         {
-            this.profileType = profileType;
+            this.profileDefinition = profileDefinition;
         }
 
         @Override
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
-            return pwmRequest.getPwmSession().getSessionManager().getProfile( pwmRequest.getPwmApplication(), profileType ) != null;
+            return pwmRequest.getPwmSession().getSessionManager().getProfile( pwmRequest.getPwmApplication(), profileDefinition ) != null;
         }
     }
 
@@ -472,12 +473,19 @@ public enum PwmIfTest
         @Override
         public boolean test( final PwmRequest pwmRequest, final PwmIfOptions options ) throws ChaiUnavailableException, PwmUnrecoverableException
         {
-            if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
+            if ( pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE ) )
             {
-                return false;
+                final Optional<PeopleSearchProfile> peopleSearchProfile = pwmRequest.isAuthenticated()
+                        ? Optional.ofNullable( pwmRequest.getPwmSession().getSessionManager().getPeopleSearchProfile( pwmRequest.getPwmApplication() ) )
+                        : pwmRequest.getConfig().getPublicPeopleSearchProfile();
+
+                if ( peopleSearchProfile.isPresent() )
+                {
+                    return peopleSearchProfile.get().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ORGCHART );
+                }
             }
 
-            return PeopleSearchConfiguration.forRequest( pwmRequest ).isOrgChartEnabled();
+            return false;
         }
     }
 

+ 1 - 1
server/src/main/java/password/pwm/ldap/LdapBrowser.java

@@ -151,7 +151,7 @@ public class LdapBrowser
         if ( !providerCache.containsKey( profile ) )
         {
             final Configuration configuration = new Configuration( storedConfiguration );
-            final LdapProfile ldapProfile = LdapProfile.makeFromStoredConfiguration( storedConfiguration, profile );
+            final LdapProfile ldapProfile = configuration.getLdapProfiles().get( profile );
             final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider( chaiProviderFactory, null, ldapProfile, configuration, null );
             providerCache.put( profile, chaiProvider );
         }

+ 16 - 30
server/src/main/java/password/pwm/ldap/LdapConnectionService.java

@@ -49,14 +49,14 @@ public class LdapConnectionService implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LdapConnectionService.class );
 
-    private final Map<LdapProfile, Map<Integer, ChaiProvider>> proxyChaiProviders = new ConcurrentHashMap<>();
-    private final Map<LdapProfile, ErrorInformation> lastLdapErrors = new ConcurrentHashMap<>();
+    private final Map<String, Map<Integer, ChaiProvider>> proxyChaiProviders = new ConcurrentHashMap<>();
+    private final Map<String, ErrorInformation> lastLdapErrors = new ConcurrentHashMap<>();
 
     private boolean useThreadLocal;
     private PwmApplication pwmApplication;
     private STATUS status = STATUS.NEW;
     private AtomicLoopIntIncrementer slotIncrementer;
-    private final ThreadLocal<Map<LdapProfile, ChaiProvider>> threadLocalProvider = new ThreadLocal<>();
+    private final ThreadLocal<Map<String, ChaiProvider>> threadLocalProvider = new ThreadLocal<>();
     private ChaiProviderFactory chaiProviderFactory;
 
     public STATUS status( )
@@ -82,7 +82,7 @@ public class LdapConnectionService implements PwmService
 
         for ( final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values() )
         {
-            proxyChaiProviders.put( ldapProfile, new ConcurrentHashMap<>() );
+            proxyChaiProviders.put( ldapProfile.getIdentifier(), new ConcurrentHashMap<>() );
         }
 
         status = STATUS.OPEN;
@@ -139,9 +139,9 @@ public class LdapConnectionService implements PwmService
 
         if ( useThreadLocal )
         {
-            if ( threadLocalProvider.get() != null && threadLocalProvider.get().containsKey( effectiveProfile ) )
+            if ( threadLocalProvider.get() != null && threadLocalProvider.get().containsKey( effectiveProfile.getIdentifier() ) )
             {
-                return threadLocalProvider.get().get( effectiveProfile );
+                return threadLocalProvider.get().get( effectiveProfile.getIdentifier() );
             }
         }
 
@@ -153,7 +153,7 @@ public class LdapConnectionService implements PwmService
             {
                 threadLocalProvider.set( new ConcurrentHashMap<>() );
             }
-            threadLocalProvider.get().put( effectiveProfile, chaiProvider );
+            threadLocalProvider.get().put( effectiveProfile.getIdentifier(), chaiProvider );
         }
 
         return chaiProvider;
@@ -169,7 +169,7 @@ public class LdapConnectionService implements PwmService
 
         final int slot = slotIncrementer.next();
 
-        final ChaiProvider proxyChaiProvider = proxyChaiProviders.get( ldapProfile ).get( slot );
+        final ChaiProvider proxyChaiProvider = proxyChaiProviders.get( ldapProfile.getIdentifier() ).get( slot );
 
         if ( proxyChaiProvider != null )
         {
@@ -185,7 +185,7 @@ public class LdapConnectionService implements PwmService
                     pwmApplication.getConfig(),
                     pwmApplication.getStatisticsManager()
             );
-            proxyChaiProviders.get( ldapProfile ).put( slot, newProvider );
+            proxyChaiProviders.get( ldapProfile.getIdentifier() ).put( slot, newProvider );
 
             return newProvider;
         }
@@ -205,25 +205,19 @@ public class LdapConnectionService implements PwmService
 
     public void setLastLdapFailure( final LdapProfile ldapProfile, final ErrorInformation errorInformation )
     {
-        lastLdapErrors.put( ldapProfile, errorInformation );
-        final HashMap<String, ErrorInformation> outputMap = new HashMap<>();
-        for ( final Map.Entry<LdapProfile, ErrorInformation> entry : lastLdapErrors.entrySet() )
-        {
-            final LdapProfile loopProfile = entry.getKey();
-            outputMap.put( loopProfile.getIdentifier(), entry.getValue() );
-        }
-        final String jsonString = JsonUtil.serialize( outputMap );
+        lastLdapErrors.put( ldapProfile.getIdentifier(), errorInformation );
+        final String jsonString = JsonUtil.serializeMap( lastLdapErrors );
         pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.LAST_LDAP_ERROR, jsonString );
     }
 
-    public Map<LdapProfile, ErrorInformation> getLastLdapFailure( )
+    public Map<String, ErrorInformation> getLastLdapFailure( )
     {
         return Collections.unmodifiableMap( lastLdapErrors );
     }
 
     public Instant getLastLdapFailureTime( final LdapProfile ldapProfile )
     {
-        final ErrorInformation errorInformation = lastLdapErrors.get( ldapProfile );
+        final ErrorInformation errorInformation = lastLdapErrors.get( ldapProfile.getIdentifier() );
         if ( errorInformation != null )
         {
             return errorInformation.getDate();
@@ -231,7 +225,7 @@ public class LdapConnectionService implements PwmService
         return null;
     }
 
-    private static Map<LdapProfile, ErrorInformation> readLastLdapFailure( final PwmApplication pwmApplication )
+    private static Map<String, ErrorInformation> readLastLdapFailure( final PwmApplication pwmApplication )
     {
         String lastLdapFailureStr = null;
         try
@@ -242,16 +236,8 @@ public class LdapConnectionService implements PwmService
                 final Map<String, ErrorInformation> fromJson = JsonUtil.deserialize( lastLdapFailureStr, new TypeToken<Map<String, ErrorInformation>>()
                 {
                 } );
-                final Map<LdapProfile, ErrorInformation> returnMap = new HashMap<>();
-                for ( final Map.Entry<String, ErrorInformation> entry : fromJson.entrySet() )
-                {
-                    final String id = entry.getKey();
-                    final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get( id );
-                    if ( ldapProfile != null )
-                    {
-                        returnMap.put( ldapProfile, entry.getValue() );
-                    }
-                }
+                final Map<String, ErrorInformation> returnMap = new HashMap<>( fromJson );
+                returnMap.keySet().retainAll( pwmApplication.getConfig().getLdapProfiles().keySet() );
                 return returnMap;
             }
         }

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

@@ -22,6 +22,7 @@ package password.pwm.ldap;
 
 import com.novell.ldapchai.ChaiConstant;
 import com.novell.ldapchai.ChaiEntry;
+import com.novell.ldapchai.ChaiEntryFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.cr.Answer;
 import com.novell.ldapchai.exception.ChaiException;
@@ -56,9 +57,9 @@ import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.EpsStatistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.JsonUtil;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -79,6 +80,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
 
@@ -862,12 +864,18 @@ public class LdapOperationsHelper
     {
         final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
         final Queue<UserIdentity> resultSet = new LinkedList<>();
+        final long searchTimeoutMs = JavaHelper.silentParseLong(
+                pwmApplication.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT ),
+                30_000 );
 
         for ( final UserPermission userPermission : permissionList )
         {
             if ( resultSet.size() < maxResults )
             {
-                final SearchConfiguration searchConfiguration = SearchConfiguration.fromPermission( userPermission );
+                final SearchConfiguration searchConfiguration = SearchConfiguration.fromPermission( userPermission )
+                        .toBuilder()
+                        .searchTimeout( searchTimeoutMs )
+                        .build();
                 final Map<UserIdentity, Map<String, String>> searchResults = userSearchEngine.performMultiUserSearch(
                         searchConfiguration,
                         maxResults - resultSet.size(),
@@ -895,65 +903,6 @@ public class LdapOperationsHelper
         };
     }
 
-
-    public static Iterator<UserIdentity> readAllUsersFromLdap(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final String searchFilter,
-            final int maxResults
-    )
-            throws PwmUnrecoverableException, PwmOperationalException
-    {
-        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
-
-        final SearchConfiguration searchConfiguration;
-        {
-            final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
-
-            builder.enableValueEscaping( false );
-            builder.searchTimeout( Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT ) ) );
-
-            if ( searchFilter == null )
-            {
-                builder.username( "*" );
-            }
-            else
-            {
-                builder.filter( searchFilter );
-            }
-
-            searchConfiguration = builder.build();
-        }
-
-        LOGGER.debug( sessionLabel, () -> "beginning user search using parameters: " + ( JsonUtil.serialize( searchConfiguration ) ) );
-
-        final Map<UserIdentity, Map<String, String>> searchResults = userSearchEngine.performMultiUserSearch(
-                searchConfiguration,
-                maxResults,
-                Collections.emptyList(),
-                sessionLabel
-
-        );
-        LOGGER.debug( sessionLabel, () -> "user search found " + searchResults.size() + " users" );
-
-        final Queue<UserIdentity> tempQueue = new LinkedList<>( searchResults.keySet() );
-
-        return new Iterator<UserIdentity>()
-        {
-            @Override
-            public boolean hasNext( )
-            {
-                return tempQueue.peek() != null;
-            }
-
-            @Override
-            public UserIdentity next( )
-            {
-                return tempQueue.poll();
-            }
-        };
-    }
-
     public static Instant readPasswordExpirationTime( final ChaiUser theUser )
     {
         try
@@ -1020,12 +969,12 @@ public class LdapOperationsHelper
         return null;
     }
 
-    public static PhotoDataBean readPhotoDataFromLdap(
+    public static Optional<PhotoDataBean> readPhotoDataFromLdap(
             final Configuration configuration,
-            final ChaiUser chaiUser,
+            final ChaiProvider chaiProvider,
             final UserIdentity userIdentity
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+            throws PwmUnrecoverableException, PwmOperationalException
     {
         final LdapProfile ldapProfile = userIdentity.getLdapProfile( configuration );
         final String attribute = ldapProfile.readSettingAsString( PwmSetting.LDAP_ATTRIBUTE_PHOTO );
@@ -1038,6 +987,7 @@ public class LdapOperationsHelper
         final String mimeType;
         try
         {
+            final ChaiUser chaiUser = ChaiEntryFactory.newChaiFactory( chaiProvider ).newChaiUser( userIdentity.getUserDN() );
             final byte[][] photoAttributeData = chaiUser.readMultiByteAttribute( attribute );
             if ( photoAttributeData == null || photoAttributeData.length == 0 || photoAttributeData[ 0 ].length == 0 )
             {
@@ -1050,7 +1000,11 @@ public class LdapOperationsHelper
         {
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_INTERNAL, "error reading user photo ldap attribute: " + e.getMessage() ) );
         }
-        return new PhotoDataBean( mimeType, ImmutableByteArray.of( photoData ) );
+        catch ( ChaiUnavailableException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
+        return Optional.of( new PhotoDataBean( mimeType, ImmutableByteArray.of( photoData ) ) );
     }
 
 

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است