Browse Source

Merge branch 'master' into xml-abstraction

jrivard@gmail.com 6 years ago
parent
commit
8fb6296ed9
47 changed files with 391 additions and 172 deletions
  1. 13 3
      docker/pom.xml
  2. 5 22
      pom.xml
  3. 1 1
      rest-test-service/pom.xml
  4. 22 2
      server/pom.xml
  5. 3 3
      server/src/main/java/password/pwm/PwmAboutProperty.java
  6. 2 0
      server/src/main/java/password/pwm/config/PwmSetting.java
  7. 20 14
      server/src/main/java/password/pwm/config/profile/PwmPasswordRule.java
  8. 9 7
      server/src/main/java/password/pwm/http/JspUtility.java
  9. 15 13
      server/src/main/java/password/pwm/http/servlet/ClientApiServlet.java
  10. 11 11
      server/src/main/java/password/pwm/http/servlet/admin/AdminServlet.java
  11. 5 2
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataBean.java
  12. 35 3
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  13. 5 15
      server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java
  14. 2 2
      server/src/main/java/password/pwm/svc/node/NodeService.java
  15. 16 9
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java
  16. 5 3
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java
  17. 43 15
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java
  18. 33 16
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java
  19. 11 4
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStorageService.java
  20. 1 1
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java
  21. 2 1
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyUserStatus.java
  22. 16 6
      server/src/main/java/password/pwm/util/PwmPasswordRuleValidator.java
  23. 10 0
      server/src/main/java/password/pwm/util/i18n/LocaleHelper.java
  24. 5 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  25. 1 1
      server/src/main/resources/password/pwm/i18n/Display.properties
  26. 1 1
      server/src/main/resources/password/pwm/i18n/Error_ca.properties
  27. 1 1
      server/src/main/resources/password/pwm/i18n/Error_da.properties
  28. 1 1
      server/src/main/resources/password/pwm/i18n/Error_de.properties
  29. 1 1
      server/src/main/resources/password/pwm/i18n/Error_en_CA.properties
  30. 1 1
      server/src/main/resources/password/pwm/i18n/Error_es.properties
  31. 1 1
      server/src/main/resources/password/pwm/i18n/Error_fr.properties
  32. 1 1
      server/src/main/resources/password/pwm/i18n/Error_fr_CA.properties
  33. 1 1
      server/src/main/resources/password/pwm/i18n/Error_it.properties
  34. 1 1
      server/src/main/resources/password/pwm/i18n/Error_iw.properties
  35. 1 1
      server/src/main/resources/password/pwm/i18n/Error_ja.properties
  36. 1 1
      server/src/main/resources/password/pwm/i18n/Error_nl.properties
  37. 1 1
      server/src/main/resources/password/pwm/i18n/Error_pl.properties
  38. 1 1
      server/src/main/resources/password/pwm/i18n/Error_pt_BR.properties
  39. 1 1
      server/src/main/resources/password/pwm/i18n/Error_ru.properties
  40. 1 1
      server/src/main/resources/password/pwm/i18n/Error_sv.properties
  41. 1 1
      server/src/main/resources/password/pwm/i18n/Error_zh_CN.properties
  42. 1 1
      server/src/main/resources/password/pwm/i18n/Error_zh_TW.properties
  43. 1 0
      server/src/main/resources/password/pwm/i18n/Message.properties
  44. 2 0
      server/src/main/resources/password/pwm/i18n/PwmSetting.properties
  45. 42 0
      server/src/test/java/password/pwm/config/profile/PwmPasswordRuleTest.java
  46. 37 0
      webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp
  47. 1 1
      webapp/src/main/webapp/public/health.jsp

+ 13 - 3
docker/pom.xml

@@ -20,6 +20,15 @@
         <project.root.basedir>${project.basedir}/..</project.root.basedir>
         <project.root.basedir>${project.basedir}/..</project.root.basedir>
     </properties>
     </properties>
 
 
+    <profiles>
+        <profile>
+            <id>skip-docker</id>
+            <properties>
+                <skipDocker>true</skipDocker>
+            </properties>
+        </profile>
+    </profiles>
+
     <build>
     <build>
         <plugins>
         <plugins>
             <plugin>
             <plugin>
@@ -34,6 +43,7 @@
                             <goal>buildTar</goal>
                             <goal>buildTar</goal>
                         </goals>
                         </goals>
                         <configuration>
                         <configuration>
+                            <skip>${skipDocker}</skip>
                             <from>
                             <from>
                                 <image>adoptopenjdk/openjdk11</image>
                                 <image>adoptopenjdk/openjdk11</image>
                             </from>
                             </from>
@@ -51,15 +61,15 @@
                                 </volumes>
                                 </volumes>
                             </container>
                             </container>
                             <extraDirectory>
                             <extraDirectory>
-                                <path>${project.basedir}/src/main/image-files</path> <!-- Copies files from 'src/main/custom-extra-dir' -->
+                                <path>${project.basedir}/src/main/image-files</path>
                                 <permissions>
                                 <permissions>
                                     <permission>
                                     <permission>
                                         <file>/app/startup.sh</file>
                                         <file>/app/startup.sh</file>
-                                        <mode>755</mode> <!-- Read/write for owner, read-only for group/other -->
+                                        <mode>755</mode>
                                     </permission>
                                     </permission>
                                     <permission>
                                     <permission>
                                         <file>/app/command.sh</file>
                                         <file>/app/command.sh</file>
-                                        <mode>755</mode> <!-- Read/write for owner, read-only for group/other -->
+                                        <mode>755</mode>
                                     </permission>
                                     </permission>
                                 </permissions>
                                 </permissions>
                             </extraDirectory>
                             </extraDirectory>

+ 5 - 22
pom.xml

@@ -64,12 +64,6 @@
                 <skipSpotbugs>true</skipSpotbugs>
                 <skipSpotbugs>true</skipSpotbugs>
             </properties>
             </properties>
         </profile>
         </profile>
-        <profile>
-            <id>skip-tests</id>
-            <properties>
-                <skipTests>true</skipTests>
-            </properties>
-        </profile>
         <profile>
         <profile>
             <id>skip-javadoc</id>
             <id>skip-javadoc</id>
             <properties>
             <properties>
@@ -80,17 +74,6 @@
 
 
     <build>
     <build>
         <plugins>
         <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-surefire-plugin</artifactId>
-                <version>2.22.0</version>
-                <configuration>
-                    <skipTests>${skipTests}</skipTests>
-                    <excludes>
-                        <exclude>**/password.pwm.manual.*</exclude>
-                    </excludes>
-                </configuration>
-            </plugin>
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <artifactId>maven-javadoc-plugin</artifactId>
@@ -188,7 +171,7 @@
                     <dependency>
                     <dependency>
                         <groupId>com.puppycrawl.tools</groupId>
                         <groupId>com.puppycrawl.tools</groupId>
                         <artifactId>checkstyle</artifactId>
                         <artifactId>checkstyle</artifactId>
-                        <version>8.15</version>
+                        <version>8.16</version>
                     </dependency>
                     </dependency>
                 </dependencies>
                 </dependencies>
                 <executions>
                 <executions>
@@ -235,12 +218,12 @@
             <plugin>
             <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
-                <version>3.1.8</version>
+                <version>3.1.10</version>
                 <dependencies>
                 <dependencies>
                     <dependency>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
                         <groupId>com.github.spotbugs</groupId>
                         <artifactId>spotbugs</artifactId>
                         <artifactId>spotbugs</artifactId>
-                        <version>3.1.9</version>
+                        <version>3.1.10</version>
                     </dependency>
                     </dependency>
                 </dependencies>
                 </dependencies>
                 <configuration>
                 <configuration>
@@ -266,7 +249,7 @@
             <plugin> <!-- checks owsp vulnerability database -->
             <plugin> <!-- checks owsp vulnerability database -->
                 <groupId>org.owasp</groupId>
                 <groupId>org.owasp</groupId>
                 <artifactId>dependency-check-maven</artifactId>
                 <artifactId>dependency-check-maven</artifactId>
-                <version>4.0.0</version>
+                <version>4.0.1</version>
                 <reportSets>
                 <reportSets>
                     <reportSet>
                     <reportSet>
                         <reports>
                         <reports>
@@ -289,7 +272,7 @@
         <dependency>
         <dependency>
             <groupId>com.github.spotbugs</groupId>
             <groupId>com.github.spotbugs</groupId>
             <artifactId>spotbugs-annotations</artifactId>
             <artifactId>spotbugs-annotations</artifactId>
-            <version>3.1.9</version>
+            <version>3.1.10</version>
             <scope>provided</scope>
             <scope>provided</scope>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>

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

@@ -75,7 +75,7 @@
         <dependency>
         <dependency>
             <groupId>com.github.tomakehurst</groupId>
             <groupId>com.github.tomakehurst</groupId>
             <artifactId>wiremock</artifactId>
             <artifactId>wiremock</artifactId>
-            <version>2.19.0</version>
+            <version>2.20.0</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>

+ 22 - 2
server/pom.xml

@@ -19,8 +19,28 @@
         <skipTests>false</skipTests>
         <skipTests>false</skipTests>
     </properties>
     </properties>
 
 
+    <profiles>
+        <profile>
+            <id>skip-tests</id>
+            <properties>
+                <skipTests>true</skipTests>
+            </properties>
+        </profile>
+    </profiles>
+
     <build>
     <build>
         <plugins>
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.22.0</version>
+                <configuration>
+                    <skipTests>${skipTests}</skipTests>
+                    <excludes>
+                        <exclude>**/password.pwm.manual.*</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
             <plugin>
             <plugin>
                 <artifactId>maven-resources-plugin</artifactId>
                 <artifactId>maven-resources-plugin</artifactId>
                 <version>3.1.0</version>
                 <version>3.1.0</version>
@@ -119,7 +139,7 @@
         <dependency>
         <dependency>
             <groupId>com.github.tomakehurst</groupId>
             <groupId>com.github.tomakehurst</groupId>
             <artifactId>wiremock</artifactId>
             <artifactId>wiremock</artifactId>
-            <version>2.19.0</version>
+            <version>2.20.0</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
@@ -170,7 +190,7 @@
         <dependency>
         <dependency>
             <groupId>com.github.ldapchai</groupId>
             <groupId>com.github.ldapchai</groupId>
             <artifactId>ldapchai</artifactId>
             <artifactId>ldapchai</artifactId>
-            <version>0.7.3</version>
+            <version>0.7.4</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>commons-net</groupId>
             <groupId>commons-net</groupId>

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

@@ -89,9 +89,9 @@ public enum PwmAboutProperty
     java_vmLocation( "Java VM Location", pwmApplication -> System.getProperty( "java.home" ) ),
     java_vmLocation( "Java VM Location", pwmApplication -> System.getProperty( "java.home" ) ),
     java_vmVersion( "Java VM Version", pwmApplication -> System.getProperty( "java.vm.version" ) ),
     java_vmVersion( "Java VM Version", pwmApplication -> System.getProperty( "java.vm.version" ) ),
     java_vmCommandLine( "Java VM Command Line", pwmApplication -> StringUtil.collectionToString( ManagementFactory.getRuntimeMXBean().getInputArguments() ) ),
     java_vmCommandLine( "Java VM Command Line", pwmApplication -> StringUtil.collectionToString( ManagementFactory.getRuntimeMXBean().getInputArguments() ) ),
-    java_osName( "Java OS Name", pwmApplication -> System.getProperty( "os.name" ) ),
-    java_osVersion( "Java OS Version", pwmApplication -> System.getProperty( "os.version" ) ),
-    java_osArch( "Java OS Architecture", pwmApplication -> System.getProperty( "os.arch" ) ),
+    java_osName( "Operating System Name", pwmApplication -> System.getProperty( "os.name" ) ),
+    java_osVersion( "Operating System Version", pwmApplication -> System.getProperty( "os.version" ) ),
+    java_osArch( "Operating System Architecture", pwmApplication -> System.getProperty( "os.arch" ) ),
     java_randomAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().pwmRandom().getAlgorithm() ),
     java_randomAlgorithm( null, pwmApplication -> pwmApplication.getSecureService().pwmRandom().getAlgorithm() ),
     java_defaultCharset( null, pwmApplication -> Charset.defaultCharset().name() ),
     java_defaultCharset( null, pwmApplication -> Charset.defaultCharset().name() ),
     java_appServerInfo( "Java AppServer Info", pwmApplication -> pwmApplication.getPwmEnvironment().getContextManager().getServerInfo() ),
     java_appServerInfo( "Java AppServer Info", pwmApplication -> pwmApplication.getPwmEnvironment().getContextManager().getServerInfo() ),

+ 2 - 0
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -491,6 +491,8 @@ public enum PwmSetting
             "password.policy.maximumAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
             "password.policy.maximumAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_MINIMUM_ALPHA(
     PASSWORD_POLICY_MINIMUM_ALPHA(
             "password.policy.minimumAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
             "password.policy.minimumAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
+    PASSWORD_POLICY_ALLOW_NON_ALPHA(
+            "password.policy.allowNonAlpha", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_MAXIMUM_NON_ALPHA(
     PASSWORD_POLICY_MAXIMUM_NON_ALPHA(
             "password.policy.maximumNonAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
             "password.policy.maximumNonAlpha", PwmSettingSyntax.NUMERIC, PwmSettingCategory.PASSWORD_POLICY ),
     PASSWORD_POLICY_MINIMUM_NON_ALPHA(
     PASSWORD_POLICY_MINIMUM_NON_ALPHA(

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

@@ -280,6 +280,26 @@ public enum PwmPasswordRule
             ChaiPasswordRule.ADComplexityMaxViolation.getDefaultValue(),
             ChaiPasswordRule.ADComplexityMaxViolation.getDefaultValue(),
             false ),
             false ),
 
 
+    AllowNonAlpha(
+            ChaiPasswordRule.AllowNonAlpha,
+            PwmSetting.PASSWORD_POLICY_ALLOW_NON_ALPHA,
+            ChaiPasswordRule.AllowNonAlpha.getRuleType(),
+            ChaiPasswordRule.AllowNonAlpha.getDefaultValue(),
+            false ),
+
+    MinimumNonAlpha(
+            ChaiPasswordRule.MinimumNonAlpha,
+            PwmSetting.PASSWORD_POLICY_MINIMUM_NON_ALPHA,
+            ChaiPasswordRule.RuleType.MIN,
+            "0",
+            false ),
+
+    MaximumNonAlpha(
+            ChaiPasswordRule.MaximumNonAlpha,
+            PwmSetting.PASSWORD_POLICY_MAXIMUM_NON_ALPHA,
+            ChaiPasswordRule.RuleType.MAX,
+            "0",
+            false ),
 
 
     // pwm specific rules
     // pwm specific rules
     // value will be imported indirectly from chai rule
     // value will be imported indirectly from chai rule
@@ -327,20 +347,6 @@ public enum PwmPasswordRule
             false
             false
     ),
     ),
 
 
-    MinimumNonAlpha(
-            null,
-            PwmSetting.PASSWORD_POLICY_MINIMUM_NON_ALPHA,
-            ChaiPasswordRule.RuleType.MIN,
-            "0",
-            false ),
-
-    MaximumNonAlpha(
-            null,
-            PwmSetting.PASSWORD_POLICY_MAXIMUM_NON_ALPHA,
-            ChaiPasswordRule.RuleType.MAX,
-            "0",
-            false ),
-
     EnableWordlist(
     EnableWordlist(
             null,
             null,
             PwmSetting.PASSWORD_POLICY_ENABLE_WORDLIST,
             PwmSetting.PASSWORD_POLICY_ENABLE_WORDLIST,

+ 9 - 7
server/src/main/java/password/pwm/http/JspUtility.java

@@ -26,7 +26,6 @@ import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.http.bean.PwmSessionBean;
-import password.pwm.i18n.Display;
 import password.pwm.i18n.PwmDisplayBundle;
 import password.pwm.i18n.PwmDisplayBundle;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
@@ -171,9 +170,7 @@ public abstract class JspUtility
     public static String friendlyWrite( final PageContext pageContext, final boolean value )
     public static String friendlyWrite( final PageContext pageContext, final boolean value )
     {
     {
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
-        return value
-                ? LocaleHelper.getLocalizedMessage( Display.Value_True, pwmRequest )
-                : LocaleHelper.getLocalizedMessage( Display.Value_False, pwmRequest );
+        return LocaleHelper.valueBoolean( pwmRequest.getLocale(), value );
     }
     }
 
 
     public static String friendlyWrite( final PageContext pageContext, final long value )
     public static String friendlyWrite( final PageContext pageContext, final long value )
@@ -185,20 +182,25 @@ public abstract class JspUtility
 
 
     public static String friendlyWrite( final PageContext pageContext, final String input )
     public static String friendlyWrite( final PageContext pageContext, final String input )
     {
     {
-        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         if ( StringUtil.isEmpty( input ) )
         if ( StringUtil.isEmpty( input ) )
         {
         {
-            return LocaleHelper.getLocalizedMessage( Display.Value_NotApplicable, pwmRequest );
+            return friendlyWriteNotApplicable( pageContext );
         }
         }
         return StringUtil.escapeHtml( input );
         return StringUtil.escapeHtml( input );
     }
     }
 
 
+    public static String friendlyWriteNotApplicable( final PageContext pageContext )
+    {
+        final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
+        return LocaleHelper.valueNotApplicable( pwmRequest.getLocale() );
+    }
+
     public static String friendlyWrite( final PageContext pageContext, final Instant instant )
     public static String friendlyWrite( final PageContext pageContext, final Instant instant )
     {
     {
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         if ( instant == null )
         if ( instant == null )
         {
         {
-            return LocaleHelper.getLocalizedMessage( Display.Value_NotApplicable, pwmRequest );
+            return LocaleHelper.valueNotApplicable( pwmRequest.getLocale() );
         }
         }
         return "<span class=\"timestamp\">" + instant.toString() + "</span>";
         return "<span class=\"timestamp\">" + instant.toString() + "</span>";
     }
     }

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

@@ -213,21 +213,23 @@ public class ClientApiServlet extends ControlledPwmServlet
     {
     {
         if ( pwmRequest.getPwmApplication().getApplicationMode() == PwmApplicationMode.RUNNING )
         if ( pwmRequest.getPwmApplication().getApplicationMode() == PwmApplicationMode.RUNNING )
         {
         {
-
-            if ( !pwmRequest.isAuthenticated() )
+            if ( !pwmRequest.getConfig().readSettingAsBoolean( PwmSetting.PUBLIC_HEALTH_STATS_WEBSERVICES ) )
             {
             {
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED );
-                LOGGER.debug( pwmRequest, errorInformation );
-                pwmRequest.respondWithError( errorInformation );
-                return ProcessStatus.Halt;
-            }
+                if ( !pwmRequest.isAuthenticated() )
+                {
+                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_AUTHENTICATION_REQUIRED );
+                    LOGGER.debug( pwmRequest, errorInformation );
+                    pwmRequest.respondWithError( errorInformation );
+                    return ProcessStatus.Halt;
+                }
 
 
-            if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmApplication(), Permission.PWMADMIN ) )
-            {
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "admin privileges required" );
-                LOGGER.debug( pwmRequest, errorInformation );
-                pwmRequest.respondWithError( errorInformation );
-                return ProcessStatus.Halt;
+                if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmRequest.getPwmApplication(), Permission.PWMADMIN ) )
+                {
+                    final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, "admin privileges required" );
+                    LOGGER.debug( pwmRequest, errorInformation );
+                    pwmRequest.respondWithError( errorInformation );
+                    return ProcessStatus.Halt;
+                }
             }
             }
         }
         }
 
 

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

@@ -56,7 +56,7 @@ import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.event.AuditRecord;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.intruder.RecordType;
 import password.pwm.svc.pwnotify.PwNotifyService;
 import password.pwm.svc.pwnotify.PwNotifyService;
-import password.pwm.svc.pwnotify.StoredJobState;
+import password.pwm.svc.pwnotify.PwNotifyStoredJobState;
 import password.pwm.svc.report.ReportCsvUtility;
 import password.pwm.svc.report.ReportCsvUtility;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.report.ReportService;
 import password.pwm.svc.report.UserCacheRecord;
 import password.pwm.svc.report.UserCacheRecord;
@@ -726,7 +726,7 @@ public class AdminServlet extends ControlledPwmServlet
         final Configuration config = pwmRequest.getConfig();
         final Configuration config = pwmRequest.getConfig();
         final Locale locale = pwmRequest.getLocale();
         final Locale locale = pwmRequest.getLocale();
         final PwNotifyService pwNotifyService = pwmRequest.getPwmApplication().getPwNotifyService();
         final PwNotifyService pwNotifyService = pwmRequest.getPwmApplication().getPwNotifyService();
-        final StoredJobState storedJobState = pwNotifyService.getJobState();
+        final PwNotifyStoredJobState pwNotifyStoredJobState = pwNotifyService.getJobState();
         final boolean canRunOnthisServer = pwNotifyService.canRunOnThisServer();
         final boolean canRunOnthisServer = pwNotifyService.canRunOnThisServer();
 
 
         statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
         statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
@@ -741,30 +741,30 @@ public class AdminServlet extends ControlledPwmServlet
                     "Next Job Scheduled Time", LocaleHelper.instantString( pwNotifyService.getNextExecutionTime(), locale, config ) ) );
                     "Next Job Scheduled Time", LocaleHelper.instantString( pwNotifyService.getNextExecutionTime(), locale, config ) ) );
         }
         }
 
 
-        if ( storedJobState != null )
+        if ( pwNotifyStoredJobState != null )
         {
         {
             statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
             statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
-                    "Last Job Start Time", LocaleHelper.instantString( storedJobState.getLastStart(), locale, config ) ) );
+                    "Last Job Start Time", LocaleHelper.instantString( pwNotifyStoredJobState.getLastStart(), locale, config ) ) );
 
 
             statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
             statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
-                    "Last Job Completion Time", LocaleHelper.instantString( storedJobState.getLastCompletion(), locale, config ) ) );
+                    "Last Job Completion Time", LocaleHelper.instantString( pwNotifyStoredJobState.getLastCompletion(), locale, config ) ) );
 
 
-            if ( storedJobState.getLastStart() != null && storedJobState.getLastCompletion() != null )
+            if ( pwNotifyStoredJobState.getLastStart() != null && pwNotifyStoredJobState.getLastCompletion() != null )
             {
             {
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.timestamp,
-                        "Last Job Duration", TimeDuration.between( storedJobState.getLastStart(), storedJobState.getLastCompletion() ).asLongString( locale ) ) );
+                        "Last Job Duration", TimeDuration.between( pwNotifyStoredJobState.getLastStart(), pwNotifyStoredJobState.getLastCompletion() ).asLongString( locale ) ) );
             }
             }
 
 
-            if ( !StringUtil.isEmpty( storedJobState.getServerInstance() ) )
+            if ( !StringUtil.isEmpty( pwNotifyStoredJobState.getServerInstance() ) )
             {
             {
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
-                        "Last Job Server Instance", storedJobState.getServerInstance() ) );
+                        "Last Job Server Instance", pwNotifyStoredJobState.getServerInstance() ) );
             }
             }
 
 
-            if ( storedJobState.getLastError() != null )
+            if ( pwNotifyStoredJobState.getLastError() != null )
             {
             {
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
                 statusData.add( new DisplayElement( String.valueOf( key++ ), DisplayElement.Type.string,
-                        "Last Job Error",  storedJobState.getLastError().toDebugStr() ) );
+                        "Last Job Error",  pwNotifyStoredJobState.getLastError().toDebugStr() ) );
             }
             }
         }
         }
 
 

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

@@ -23,17 +23,18 @@
 package password.pwm.http.servlet.admin;
 package password.pwm.http.servlet.admin;
 
 
 import lombok.Builder;
 import lombok.Builder;
-import lombok.Getter;
+import lombok.Value;
 import password.pwm.Permission;
 import password.pwm.Permission;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.bean.pub.PublicUserInfoBean;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.ProfileType;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
+import password.pwm.svc.pwnotify.PwNotifyUserStatus;
 
 
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.Map;
 import java.util.Map;
 
 
-@Getter
+@Value
 @Builder
 @Builder
 public class UserDebugDataBean implements Serializable
 public class UserDebugDataBean implements Serializable
 {
 {
@@ -47,4 +48,6 @@ public class UserDebugDataBean implements Serializable
     private final PwmPasswordPolicy ldapPasswordPolicy;
     private final PwmPasswordPolicy ldapPasswordPolicy;
     private final PwmPasswordPolicy configuredPasswordPolicy;
     private final PwmPasswordPolicy configuredPasswordPolicy;
     private final Map<ProfileType, String> profiles;
     private final Map<ProfileType, String> profiles;
+
+    private final PwNotifyUserStatus pwNotifyUserStatus;
 }
 }

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

@@ -38,6 +38,9 @@ import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.svc.PwmService;
+import password.pwm.svc.pwnotify.PwNotifyUserStatus;
+import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.macro.MacroMachine;
 import password.pwm.util.operations.PasswordUtility;
 import password.pwm.util.operations.PasswordUtility;
 
 
@@ -45,10 +48,13 @@ import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.TreeMap;
 import java.util.TreeMap;
 
 
 public class UserDebugDataReader
 public class UserDebugDataReader
 {
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserDebugDataReader.class );
+
     public static UserDebugDataBean readUserDebugData(
     public static UserDebugDataBean readUserDebugData(
             final PwmApplication pwmApplication,
             final PwmApplication pwmApplication,
             final Locale locale,
             final Locale locale,
@@ -86,7 +92,9 @@ public class UserDebugDataReader
 
 
         final MacroMachine macroMachine = MacroMachine.forUser( pwmApplication, locale, sessionLabel, userIdentity );
         final MacroMachine macroMachine = MacroMachine.forUser( pwmApplication, locale, sessionLabel, userIdentity );
 
 
-        final UserDebugDataBean userDebugData = UserDebugDataBean.builder()
+        final PwNotifyUserStatus pwNotifyUserStatus = readPwNotifyUserStatus( pwmApplication, userIdentity, sessionLabel );
+
+        return UserDebugDataBean.builder()
                 .userInfo( userInfo )
                 .userInfo( userInfo )
                 .publicUserInfoBean( PublicUserInfoBean.fromUserInfoBean( userInfo, pwmApplication.getConfig(), locale, macroMachine ) )
                 .publicUserInfoBean( PublicUserInfoBean.fromUserInfoBean( userInfo, pwmApplication.getConfig(), locale, macroMachine ) )
                 .permissions( permissions )
                 .permissions( permissions )
@@ -95,9 +103,8 @@ public class UserDebugDataReader
                 .configuredPasswordPolicy( configPasswordPolicy )
                 .configuredPasswordPolicy( configPasswordPolicy )
                 .passwordReadable( readablePassword )
                 .passwordReadable( readablePassword )
                 .passwordWithinMinimumLifetime( userInfo.isWithinPasswordMinimumLifetime() )
                 .passwordWithinMinimumLifetime( userInfo.isWithinPasswordMinimumLifetime() )
+                .pwNotifyUserStatus( pwNotifyUserStatus )
                 .build();
                 .build();
-
-        return userDebugData;
     }
     }
 
 
 
 
@@ -148,4 +155,29 @@ public class UserDebugDataReader
         }
         }
         return Collections.unmodifiableMap( results );
         return Collections.unmodifiableMap( results );
     }
     }
+
+    private static PwNotifyUserStatus readPwNotifyUserStatus(
+            final PwmApplication pwmApplication,
+            final UserIdentity userIdentity,
+            final SessionLabel sessionLabel
+    )
+    {
+        if ( pwmApplication.getPwNotifyService().status() == PwmService.STATUS.OPEN )
+        {
+            try
+            {
+                final Optional<PwNotifyUserStatus> value = pwmApplication.getPwNotifyService().readUserNotificationState( userIdentity, sessionLabel );
+                if ( value.isPresent() )
+                {
+                    return value.get();
+                }
+            }
+            catch ( PwmUnrecoverableException e )
+            {
+                LOGGER.debug( () -> "error reading user pwNotify status: " + e.getMessage() );
+            }
+        }
+
+        return null;
+    }
 }
 }

+ 5 - 15
server/src/main/java/password/pwm/svc/node/LDAPNodeDataService.java

@@ -33,6 +33,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
@@ -51,24 +52,13 @@ class LDAPNodeDataService implements NodeDataServiceProvider
     {
     {
         this.pwmApplication = pwmApplication;
         this.pwmApplication = pwmApplication;
 
 
-        final UserIdentity testUser;
-        final String ldapProfileID;
-        try
-        {
-            final LdapProfile ldapProfile = pwmApplication.getConfig().getDefaultLdapProfile();
-            ldapProfileID = ldapProfile.getIdentifier();
-            testUser = ldapProfile.getTestUser( pwmApplication );
-        }
-        catch ( PwmUnrecoverableException e )
-        {
-            final String msg = "error checking ldap test user configuration for ldap node service: " + e.getMessage();
-            throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
-        }
+        final LdapProfile ldapProfile = pwmApplication.getConfig().getDefaultLdapProfile();
+        final String testUser = ldapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
 
 
-        if ( testUser == null )
+        if ( StringUtil.isEmpty( testUser ) )
         {
         {
             final String msg = "ldap node service requires that setting "
             final String msg = "ldap node service requires that setting "
-                    + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfileID, null )
+                    + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( ldapProfile.getIdentifier(), null )
                     + " is configured";
                     + " is configured";
             throw PwmUnrecoverableException.newException( PwmError.ERROR_NODE_SERVICE_ERROR, msg );
             throw PwmUnrecoverableException.newException( PwmError.ERROR_NODE_SERVICE_ERROR, msg );
         }
         }

+ 2 - 2
server/src/main/java/password/pwm/svc/node/NodeService.java

@@ -114,11 +114,11 @@ public class NodeService implements PwmService
         catch ( PwmUnrecoverableException e )
         catch ( PwmUnrecoverableException e )
         {
         {
             startupError = e.getErrorInformation();
             startupError = e.getErrorInformation();
-            LOGGER.error( "error starting up cluster service: " + e.getMessage() );
+            LOGGER.error( "error starting up node service: " + e.getMessage() );
         }
         }
         catch ( Exception e )
         catch ( Exception e )
         {
         {
-            startupError = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, "error starting up cluster service: " + e.getMessage() );
+            startupError = new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, "error starting up node service: " + e.getMessage() );
             LOGGER.error( startupError );
             LOGGER.error( startupError );
         }
         }
 
 

+ 16 - 9
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java

@@ -35,6 +35,8 @@ import password.pwm.util.db.DatabaseTable;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 
 
+import java.util.Optional;
+
 class PwNotifyDbStorageService implements PwNotifyStorageService
 class PwNotifyDbStorageService implements PwNotifyStorageService
 {
 {
     private static final String DB_STATE_STRING = "PwNotifyJobState";
     private static final String DB_STATE_STRING = "PwNotifyJobState";
@@ -54,7 +56,7 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
     }
     }
 
 
     @Override
     @Override
-    public StoredNotificationState readStoredUserState(
+    public Optional<PwNotifyUserStatus> readStoredUserState(
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final SessionLabel sessionLabel
             final SessionLabel sessionLabel
     )
     )
@@ -84,13 +86,18 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) );
             throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_DB_UNAVAILABLE, e.getMessage() ) );
         }
         }
 
 
-        return JsonUtil.deserialize( rawDbValue, StoredNotificationState.class );
+        if ( !StringUtil.isEmpty( rawDbValue ) )
+        {
+            return Optional.ofNullable( JsonUtil.deserialize( rawDbValue, PwNotifyUserStatus.class ) );
+        }
+
+        return Optional.empty();
     }
     }
 
 
     public void writeStoredUserState(
     public void writeStoredUserState(
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
-            final StoredNotificationState storedNotificationState
+            final PwNotifyUserStatus pwNotifyUserStatus
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
@@ -108,7 +115,7 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
             throw new PwmUnrecoverableException( PwmError.ERROR_MISSING_GUID );
             throw new PwmUnrecoverableException( PwmError.ERROR_MISSING_GUID );
         }
         }
 
 
-        final String rawDbValue = JsonUtil.serialize( storedNotificationState );
+        final String rawDbValue = JsonUtil.serialize( pwNotifyUserStatus );
         try
         try
         {
         {
             pwmApplication.getDatabaseAccessor().put( TABLE, guid, rawDbValue );
             pwmApplication.getDatabaseAccessor().put( TABLE, guid, rawDbValue );
@@ -120,7 +127,7 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
     }
     }
 
 
     @Override
     @Override
-    public StoredJobState readStoredJobState()
+    public PwNotifyStoredJobState readStoredJobState()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         try
         try
@@ -128,9 +135,9 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
             final String strValue = pwmApplication.getDatabaseService().getAccessor().get( DatabaseTable.PW_NOTIFY, DB_STATE_STRING );
             final String strValue = pwmApplication.getDatabaseService().getAccessor().get( DatabaseTable.PW_NOTIFY, DB_STATE_STRING );
             if ( StringUtil.isEmpty( strValue ) )
             if ( StringUtil.isEmpty( strValue ) )
             {
             {
-                return new StoredJobState( null, null, null, null, false );
+                return new PwNotifyStoredJobState( null, null, null, null, false );
             }
             }
-            return JsonUtil.deserialize( strValue, StoredJobState.class );
+            return JsonUtil.deserialize( strValue, PwNotifyStoredJobState.class );
         }
         }
         catch ( DatabaseException e )
         catch ( DatabaseException e )
         {
         {
@@ -139,12 +146,12 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
     }
     }
 
 
     @Override
     @Override
-    public void writeStoredJobState( final StoredJobState storedJobState )
+    public void writeStoredJobState( final PwNotifyStoredJobState pwNotifyStoredJobState )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         try
         try
         {
         {
-            final String strValue = JsonUtil.serialize( storedJobState );
+            final String strValue = JsonUtil.serialize( pwNotifyStoredJobState );
             pwmApplication.getDatabaseService().getAccessor().put( DatabaseTable.PW_NOTIFY, DB_STATE_STRING, strValue );
             pwmApplication.getDatabaseService().getAccessor().put( DatabaseTable.PW_NOTIFY, DB_STATE_STRING, strValue );
         }
         }
         catch ( DatabaseException e )
         catch ( DatabaseException e )

+ 5 - 3
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java

@@ -52,6 +52,7 @@ import java.time.temporal.ChronoUnit;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -261,7 +262,7 @@ public class PwNotifyEngine
         }
         }
 
 
         log( "sending notice to " + userIdentity.toDisplayString() + " for interval " + nextDayInterval );
         log( "sending notice to " + userIdentity.toDisplayString() + " for interval " + nextDayInterval );
-        storageService.writeStoredUserState( userIdentity, SESSION_LABEL, new StoredNotificationState( passwordExpirationTime, Instant.now(), nextDayInterval ) );
+        storageService.writeStoredUserState( userIdentity, SESSION_LABEL, new PwNotifyUserStatus( passwordExpirationTime, Instant.now(), nextDayInterval ) );
         sendNoticeEmail( userIdentity );
         sendNoticeEmail( userIdentity );
     }
     }
 
 
@@ -296,13 +297,14 @@ public class PwNotifyEngine
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StoredNotificationState storedState = storageService.readStoredUserState( userIdentity, SESSION_LABEL );
+        final Optional<PwNotifyUserStatus> optionalStoredState = storageService.readStoredUserState( userIdentity, SESSION_LABEL );
 
 
-        if ( storedState == null )
+        if ( !optionalStoredState.isPresent() )
         {
         {
             return false;
             return false;
         }
         }
 
 
+        final PwNotifyUserStatus storedState = optionalStoredState.get();
         if ( storedState.getExpireTime() == null || !storedState.getExpireTime().equals( passwordExpirationTime ) )
         if ( storedState.getExpireTime() == null || !storedState.getExpireTime().equals( passwordExpirationTime ) )
         {
         {
             return false;
             return false;

+ 43 - 15
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyLdapStorageService.java

@@ -38,8 +38,14 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 
 
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
 class PwNotifyLdapStorageService implements PwNotifyStorageService
 class PwNotifyLdapStorageService implements PwNotifyStorageService
 {
 {
+    private static final String COR_GUID = ".";
+
     private final PwmApplication pwmApplication;
     private final PwmApplication pwmApplication;
     private final PwNotifySettings settings;
     private final PwNotifySettings settings;
 
 
@@ -67,11 +73,12 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
         this.pwmApplication = pwmApplication;
         this.pwmApplication = pwmApplication;
         this.settings = settings;
         this.settings = settings;
 
 
-        final UserIdentity userIdentity = pwmApplication.getConfig().getDefaultLdapProfile().getTestUser( pwmApplication );
-        if ( userIdentity == null )
+        final LdapProfile defaultLdapProfile = pwmApplication.getConfig().getDefaultLdapProfile();
+        final String testUserDN = defaultLdapProfile.readSettingAsString( PwmSetting.LDAP_TEST_USER_DN );
+        if ( StringUtil.isEmpty( testUserDN ) )
         {
         {
             final String msg = "LDAP storage type selected, but LDAP test user ("
             final String msg = "LDAP storage type selected, but LDAP test user ("
-                    + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( pwmApplication.getConfig().getDefaultLdapProfile().getIdentifier(), PwmConstants.DEFAULT_LOCALE )
+                    + PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug( defaultLdapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE )
                     + ") not defined.";
                     + ") not defined.";
             throw new PwmUnrecoverableException( PwmError.ERROR_PWNOTIFY_SERVICE_ERROR, msg );
             throw new PwmUnrecoverableException( PwmError.ERROR_PWNOTIFY_SERVICE_ERROR, msg );
         }
         }
@@ -90,7 +97,7 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
     }
     }
 
 
     @Override
     @Override
-    public StoredNotificationState readStoredUserState(
+    public Optional<PwNotifyUserStatus> readStoredUserState(
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final SessionLabel sessionLabel
             final SessionLabel sessionLabel
     )
     )
@@ -98,22 +105,23 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
     {
     {
         final ConfigObjectRecord configObjectRecord = getUserCOR( userIdentity, CoreType.User );
         final ConfigObjectRecord configObjectRecord = getUserCOR( userIdentity, CoreType.User );
         final String payload = configObjectRecord.getPayload();
         final String payload = configObjectRecord.getPayload();
-        if ( StringUtil.isEmpty( payload ) )
+        if ( !StringUtil.isEmpty( payload ) )
         {
         {
-            return JsonUtil.deserialize( payload, StoredNotificationState.class );
+            return Optional.ofNullable( JsonUtil.deserialize( payload, PwNotifyUserStatus.class ) );
         }
         }
-        return null;
+
+        return Optional.empty();
     }
     }
 
 
     public void writeStoredUserState(
     public void writeStoredUserState(
             final UserIdentity userIdentity,
             final UserIdentity userIdentity,
             final SessionLabel sessionLabel,
             final SessionLabel sessionLabel,
-            final StoredNotificationState storedNotificationState
+            final PwNotifyUserStatus pwNotifyUserStatus
     )
     )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final ConfigObjectRecord configObjectRecord = getUserCOR( userIdentity, CoreType.User );
         final ConfigObjectRecord configObjectRecord = getUserCOR( userIdentity, CoreType.User );
-        final String payload = JsonUtil.serialize( storedNotificationState );
+        final String payload = JsonUtil.serialize( pwNotifyUserStatus );
         try
         try
         {
         {
             configObjectRecord.updatePayload( payload );
             configObjectRecord.updatePayload( payload );
@@ -131,7 +139,7 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
     }
     }
 
 
     @Override
     @Override
-    public StoredJobState readStoredJobState()
+    public PwNotifyStoredJobState readStoredJobState()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final UserIdentity proxyUser = pwmApplication.getConfig().getDefaultLdapProfile().getTestUser( pwmApplication );
         final UserIdentity proxyUser = pwmApplication.getConfig().getDefaultLdapProfile().getTestUser( pwmApplication );
@@ -140,18 +148,18 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
 
 
         if ( StringUtil.isEmpty( payload ) )
         if ( StringUtil.isEmpty( payload ) )
         {
         {
-            return new StoredJobState( null, null, null, null, false );
+            return new PwNotifyStoredJobState( null, null, null, null, false );
         }
         }
-        return JsonUtil.deserialize( payload, StoredJobState.class );
+        return JsonUtil.deserialize( payload, PwNotifyStoredJobState.class );
     }
     }
 
 
     @Override
     @Override
-    public void writeStoredJobState( final StoredJobState storedJobState )
+    public void writeStoredJobState( final PwNotifyStoredJobState pwNotifyStoredJobState )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         final UserIdentity proxyUser = pwmApplication.getConfig().getDefaultLdapProfile().getTestUser( pwmApplication );
         final UserIdentity proxyUser = pwmApplication.getConfig().getDefaultLdapProfile().getTestUser( pwmApplication );
         final ConfigObjectRecord configObjectRecord = getUserCOR( proxyUser, CoreType.ProxyUser );
         final ConfigObjectRecord configObjectRecord = getUserCOR( proxyUser, CoreType.ProxyUser );
-        final String payload = JsonUtil.serialize( storedJobState );
+        final String payload = JsonUtil.serialize( pwNotifyStoredJobState );
 
 
         try
         try
         {
         {
@@ -174,7 +182,27 @@ class PwNotifyLdapStorageService implements PwNotifyStorageService
     {
     {
         final String userAttr = getLdapUserAttribute( userIdentity );
         final String userAttr = getLdapUserAttribute( userIdentity );
         final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser( userIdentity );
         final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser( userIdentity );
-        return ConfigObjectRecord.createNew( chaiUser, userAttr, coreType.getRecordID(), null, null );
+        try
+        {
+            final List<ConfigObjectRecord> list = ConfigObjectRecord.readRecordFromLDAP(
+                    chaiUser,
+                    userAttr,
+                    coreType.getRecordID(),
+                    Collections.singleton( COR_GUID ),
+                    Collections.singleton( COR_GUID ) );
+            if ( list.isEmpty() )
+            {
+                return ConfigObjectRecord.createNew( chaiUser, userAttr, coreType.getRecordID(), COR_GUID, COR_GUID );
+            }
+            else
+            {
+                return list.iterator().next();
+            }
+        }
+        catch ( ChaiUnavailableException | ChaiOperationException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
     }
     }
 
 
     private String getLdapUserAttribute( final UserIdentity userIdentity )
     private String getLdapUserAttribute( final UserIdentity userIdentity )

+ 33 - 16
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java

@@ -24,6 +24,7 @@ package password.pwm.svc.pwnotify;
 
 
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.ErrorInformation;
@@ -47,6 +48,7 @@ import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ExecutorService;
 
 
 public class PwNotifyService extends AbstractPwmService implements PwmService
 public class PwNotifyService extends AbstractPwmService implements PwmService
@@ -62,16 +64,16 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
 
 
     private DataStorageMethod storageMethod;
     private DataStorageMethod storageMethod;
 
 
-    public StoredJobState getJobState() throws PwmUnrecoverableException
+    public PwNotifyStoredJobState getJobState() throws PwmUnrecoverableException
     {
     {
         if ( status() != STATUS.OPEN )
         if ( status() != STATUS.OPEN )
         {
         {
             if ( getStartupError() != null )
             if ( getStartupError() != null )
             {
             {
-                return StoredJobState.builder().lastError( getStartupError() ).build();
+                return PwNotifyStoredJobState.builder().lastError( getStartupError() ).build();
             }
             }
 
 
-            return StoredJobState.builder().build();
+            return PwNotifyStoredJobState.builder().build();
         }
         }
 
 
         return storageService.readStoredJobState();
         return storageService.readStoredJobState();
@@ -113,7 +115,7 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
         {
         {
             if ( pwmApplication.getClusterService() == null || pwmApplication.getClusterService().status() != STATUS.OPEN )
             if ( pwmApplication.getClusterService() == null || pwmApplication.getClusterService().status() != STATUS.OPEN )
             {
             {
-                throw PwmUnrecoverableException.newException( PwmError.ERROR_PWNOTIFY_SERVICE_ERROR, "will remain closed, cluster service is not running" );
+                throw PwmUnrecoverableException.newException( PwmError.ERROR_PWNOTIFY_SERVICE_ERROR, "will remain closed, node service is not running" );
             }
             }
 
 
             settings = PwNotifySettings.fromConfiguration( pwmApplication.getConfig() );
             settings = PwNotifySettings.fromConfiguration( pwmApplication.getConfig() );
@@ -174,17 +176,18 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
     private Instant figureNextJobExecutionTime()
     private Instant figureNextJobExecutionTime()
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final StoredJobState storedJobState = storageService.readStoredJobState();
-        if ( storedJobState != null )
+        final PwNotifyStoredJobState pwNotifyStoredJobState = storageService.readStoredJobState();
+        if ( pwNotifyStoredJobState != null )
         {
         {
             // never run, or last job not successful.
             // never run, or last job not successful.
-            if ( storedJobState.getLastCompletion() == null || storedJobState.getLastError() != null )
+            if ( pwNotifyStoredJobState.getLastCompletion() == null || pwNotifyStoredJobState.getLastError() != null )
             {
             {
                 return Instant.now().plus( 1, ChronoUnit.MINUTES );
                 return Instant.now().plus( 1, ChronoUnit.MINUTES );
             }
             }
 
 
             // more than 24hr ago.
             // more than 24hr ago.
-            if ( Duration.between( Instant.now(), storedJobState.getLastCompletion() ).abs().getSeconds() > settings.getMaximumSkipWindow().as( TimeDuration.Unit.SECONDS ) )
+            final long maxSeconds = settings.getMaximumSkipWindow().as( TimeDuration.Unit.SECONDS );
+            if ( Duration.between( Instant.now(), pwNotifyStoredJobState.getLastCompletion() ).abs().getSeconds() > maxSeconds )
             {
             {
                 return Instant.now();
                 return Instant.now();
             }
             }
@@ -220,10 +223,10 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
 
 
         try
         try
         {
         {
-            final StoredJobState storedJobState = storageService.readStoredJobState();
-            if ( storedJobState != null )
+            final PwNotifyStoredJobState pwNotifyStoredJobState = storageService.readStoredJobState();
+            if ( pwNotifyStoredJobState != null )
             {
             {
-                final ErrorInformation errorInformation = storedJobState.getLastError();
+                final ErrorInformation errorInformation = pwNotifyStoredJobState.getLastError();
                 if ( errorInformation != null )
                 if ( errorInformation != null )
                 {
                 {
                     returnRecords.add( HealthRecord.forMessage( HealthMessage.PwNotify_Failure, errorInformation.toDebugStr() ) );
                     returnRecords.add( HealthRecord.forMessage( HealthMessage.PwNotify_Failure, errorInformation.toDebugStr() ) );
@@ -305,13 +308,13 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
             final Instant start = Instant.now();
             final Instant start = Instant.now();
             try
             try
             {
             {
-                storageService.writeStoredJobState( new StoredJobState( Instant.now(), null, pwmApplication.getInstanceID(), null, false ) );
+                storageService.writeStoredJobState( new PwNotifyStoredJobState( Instant.now(), null, pwmApplication.getInstanceID(), null, false ) );
                 StatisticsManager.incrementStat( pwmApplication, Statistic.PWNOTIFY_JOBS );
                 StatisticsManager.incrementStat( pwmApplication, Statistic.PWNOTIFY_JOBS );
                 engine.executeJob();
                 engine.executeJob();
 
 
                 final Instant finish = Instant.now();
                 final Instant finish = Instant.now();
-                final StoredJobState storedJobState = new StoredJobState( start, finish, pwmApplication.getInstanceID(), null, true );
-                storageService.writeStoredJobState( storedJobState );
+                final PwNotifyStoredJobState pwNotifyStoredJobState = new PwNotifyStoredJobState( start, finish, pwmApplication.getInstanceID(), null, true );
+                storageService.writeStoredJobState( pwNotifyStoredJobState );
             }
             }
             catch ( Exception e )
             catch ( Exception e )
             {
             {
@@ -327,11 +330,11 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
 
 
                 final Instant finish = Instant.now();
                 final Instant finish = Instant.now();
                 final String instanceID = pwmApplication.getInstanceID();
                 final String instanceID = pwmApplication.getInstanceID();
-                final StoredJobState storedJobState = new StoredJobState( start, finish, instanceID, errorInformation, false );
+                final PwNotifyStoredJobState pwNotifyStoredJobState = new PwNotifyStoredJobState( start, finish, instanceID, errorInformation, false );
 
 
                 try
                 try
                 {
                 {
-                    storageService.writeStoredJobState( storedJobState );
+                    storageService.writeStoredJobState( pwNotifyStoredJobState );
                 }
                 }
                 catch ( Exception e2 )
                 catch ( Exception e2 )
                 {
                 {
@@ -343,4 +346,18 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
             }
             }
         }
         }
     }
     }
+
+    public Optional<PwNotifyUserStatus> readUserNotificationState(
+            final UserIdentity userIdentity,
+            final SessionLabel sessionLabel
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( status() == STATUS.OPEN )
+        {
+            return storageService.readStoredUserState( userIdentity, sessionLabel );
+        }
+
+        throw PwmUnrecoverableException.newException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, "pwnotify service is not open" );
+    }
 }
 }

+ 11 - 4
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStorageService.java

@@ -26,20 +26,27 @@ import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 
 
+import java.util.Optional;
+
 interface PwNotifyStorageService
 interface PwNotifyStorageService
 {
 {
 
 
-    StoredNotificationState readStoredUserState(
+    Optional<PwNotifyUserStatus> readStoredUserState(
             UserIdentity userIdentity,
             UserIdentity userIdentity,
             SessionLabel sessionLabel
             SessionLabel sessionLabel
     )
     )
             throws PwmUnrecoverableException;
             throws PwmUnrecoverableException;
 
 
-    void writeStoredUserState( UserIdentity userIdentity, SessionLabel sessionLabel, StoredNotificationState storedNotificationState ) throws PwmUnrecoverableException;
+    void writeStoredUserState(
+            UserIdentity userIdentity,
+            SessionLabel sessionLabel,
+            PwNotifyUserStatus pwNotifyUserStatus
+    )
+            throws PwmUnrecoverableException;
 
 
-    StoredJobState readStoredJobState()
+    PwNotifyStoredJobState readStoredJobState()
             throws PwmUnrecoverableException;
             throws PwmUnrecoverableException;
 
 
-    void writeStoredJobState( StoredJobState storedJobState )
+    void writeStoredJobState( PwNotifyStoredJobState pwNotifyStoredJobState )
                     throws PwmUnrecoverableException;
                     throws PwmUnrecoverableException;
 }
 }

+ 1 - 1
server/src/main/java/password/pwm/svc/pwnotify/StoredJobState.java → server/src/main/java/password/pwm/svc/pwnotify/PwNotifyStoredJobState.java

@@ -31,7 +31,7 @@ import java.time.Instant;
 
 
 @Value
 @Value
 @Builder
 @Builder
-public class StoredJobState implements Serializable
+public class PwNotifyStoredJobState implements Serializable
 {
 {
     private Instant lastStart;
     private Instant lastStart;
     private Instant lastCompletion;
     private Instant lastCompletion;

+ 2 - 1
server/src/main/java/password/pwm/svc/pwnotify/StoredNotificationState.java → server/src/main/java/password/pwm/svc/pwnotify/PwNotifyUserStatus.java

@@ -28,7 +28,8 @@ import java.io.Serializable;
 import java.time.Instant;
 import java.time.Instant;
 
 
 @Value
 @Value
-class StoredNotificationState implements Serializable
+public
+class PwNotifyUserStatus implements Serializable
 {
 {
     private Instant expireTime;
     private Instant expireTime;
     private Instant lastNotice;
     private Instant lastNotice;

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

@@ -885,15 +885,25 @@ public class PwmPasswordRuleValidator
         {
         {
             final int numberOfNonAlphaChars = charCounter.getNonAlphaCharCount();
             final int numberOfNonAlphaChars = charCounter.getNonAlphaCharCount();
 
 
-            if ( numberOfNonAlphaChars < ruleHelper.readIntValue( PwmPasswordRule.MinimumNonAlpha ) )
+            if ( ruleHelper.readBooleanValue( PwmPasswordRule.AllowNonAlpha ) )
             {
             {
-                errorList.add( new ErrorInformation( PwmError.PASSWORD_NOT_ENOUGH_NONALPHA ) );
-            }
+                if ( numberOfNonAlphaChars < ruleHelper.readIntValue( PwmPasswordRule.MinimumNonAlpha ) )
+                {
+                    errorList.add( new ErrorInformation( PwmError.PASSWORD_NOT_ENOUGH_NONALPHA ) );
+                }
 
 
-            final int maxNonAlpha = ruleHelper.readIntValue( PwmPasswordRule.MaximumNonAlpha );
-            if ( maxNonAlpha > 0 && numberOfNonAlphaChars > maxNonAlpha )
+                final int maxNonAlpha = ruleHelper.readIntValue( PwmPasswordRule.MaximumNonAlpha );
+                if ( maxNonAlpha > 0 && numberOfNonAlphaChars > maxNonAlpha )
+                {
+                    errorList.add( new ErrorInformation( PwmError.PASSWORD_TOO_MANY_NONALPHA ) );
+                }
+            }
+            else
             {
             {
-                errorList.add( new ErrorInformation( PwmError.PASSWORD_TOO_MANY_NONALPHA ) );
+                if ( numberOfNonAlphaChars > 0 )
+                {
+                    errorList.add( new ErrorInformation( PwmError.PASSWORD_TOO_MANY_NONALPHA ) );
+                }
             }
             }
         }
         }
 
 

+ 10 - 0
server/src/main/java/password/pwm/util/i18n/LocaleHelper.java

@@ -471,4 +471,14 @@ public class LocaleHelper
         return new ArrayList<>( returnMap.values() );
         return new ArrayList<>( returnMap.values() );
     }
     }
 
 
+    public static String valueBoolean( final Locale locale, final boolean value )
+    {
+        final PwmDisplayBundle key = value ? Display.Value_True : Display.Value_False;
+        return getLocalizedMessage( locale, key, null );
+    }
+
+    public static String valueNotApplicable( final Locale locale )
+    {
+        return getLocalizedMessage( locale, Display.Value_NotApplicable, null );
+    }
 }
 }

+ 5 - 0
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -1193,6 +1193,11 @@
             <value>0</value>
             <value>0</value>
         </default>
         </default>
     </setting>
     </setting>
+    <setting hidden="false" key="password.policy.allowNonAlpha" level="1" required="true">
+        <default>
+            <value>true</value>
+        </default>
+    </setting>
     <setting hidden="false" key="password.policy.maximumNonAlpha" level="1" required="true">
     <setting hidden="false" key="password.policy.maximumNonAlpha" level="1" required="true">
         <default>
         <default>
             <value>0</value>
             <value>0</value>

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

@@ -373,7 +373,7 @@ Value_Default=Default
 Value_ProgressComplete=Complete
 Value_ProgressComplete=Complete
 Value_ProgressInProgress=In Progress
 Value_ProgressInProgress=In Progress
 Placeholder_Search=Search
 Placeholder_Search=Search
-Instructions_ExportOrgChart1=Click the Export button to begin download of the organizational chart data. After download is complete, you can import the data into a program like Visio so it can be formatted into the desired output.
+Instructions_ExportOrgChart1=Click the Export button to begin download of the organizational chart data. After download is complete, you can use the data to import this org chart data into a program of your choice. The data is exported in CSV format.
 Instructions_ExportOrgChart2=Choose the export level depth, and press the Export button below.
 Instructions_ExportOrgChart2=Choose the export level depth, and press the Export button below.
 Instructions_EmailTeam1=The email list is generated based off of organizational data starting at this point. When you click the Send Email button, your default email program should automatically open, with the list of email addresses pre-filled.
 Instructions_EmailTeam1=The email list is generated based off of organizational data starting at this point. When you click the Send Email button, your default email program should automatically open, with the list of email addresses pre-filled.
 Instructions_EmailTeam2=Choose the team level depth, and press the Email button below.
 Instructions_EmailTeam2=Choose the team level depth, and press the Email button below.

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_ca.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=La contrasenya \u00e9s incorrecta; torneu-ho a provar.
 Error_RecoverySequenceIncomplete=Hi ha hagut un problema durant la seq\u00fc\u00e8ncia per recuperar la contrasenya oblidada. Torni-ho a provar.
 Error_RecoverySequenceIncomplete=Hi ha hagut un problema durant la seq\u00fc\u00e8ncia per recuperar la contrasenya oblidada. Torni-ho a provar.
 Error_FileTypeIncorrect=El tipus de fitxer no \u00e9s correcte.
 Error_FileTypeIncorrect=El tipus de fitxer no \u00e9s correcte.
 Error_FileTooLarge=El fitxer \u00e9s massa gran.
 Error_FileTooLarge=El fitxer \u00e9s massa gran.
-Error_ClusterServiceError=Hi ha hagut un error al servei del cl\u00faster: %1%.   Consulti els fitxers de registre per obtenir-ne m\u00e9s informaci\u00f3.
+Error_NodeServiceError=Hi ha hagut un error al servei del cl\u00faster: %1%.   Consulti els fitxers de registre per obtenir-ne m\u00e9s informaci\u00f3.
 Error_RemoteErrorValue=Error remot: %1%
 Error_RemoteErrorValue=Error remot: %1%
 Error_WordlistImportError=Hi ha hagut un error en importar la llista de paraules: %1%
 Error_WordlistImportError=Hi ha hagut un error en importar la llista de paraules: %1%
 Error_PwNotifyServiceError=Hi ha hagut un error en executar el servei de notificaci\u00f3 de contrasenyes: %1%
 Error_PwNotifyServiceError=Hi ha hagut un error en executar el servei de notificaci\u00f3 de contrasenyes: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_da.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Adgangskoden er forkert. Pr\u00f8v igen.
 Error_RecoverySequenceIncomplete=Der opstod et problem under sekvensen med den glemte adgangskode. Pr\u00f8v igen.
 Error_RecoverySequenceIncomplete=Der opstod et problem under sekvensen med den glemte adgangskode. Pr\u00f8v igen.
 Error_FileTypeIncorrect=Filtypen er ikke korrekt.
 Error_FileTypeIncorrect=Filtypen er ikke korrekt.
 Error_FileTooLarge=Filen er for stor.
 Error_FileTooLarge=Filen er for stor.
-Error_ClusterServiceError=Der opstod en fejl med klyngetjenesten: %1%. Kontroll\u00e9r logfilerne for at f\u00e5 flere oplysninger.
+Error_NodeServiceError=Der opstod en fejl med klyngetjenesten: %1%. Kontroll\u00e9r logfilerne for at f\u00e5 flere oplysninger.
 Error_RemoteErrorValue=Fjernfejl: %1%
 Error_RemoteErrorValue=Fjernfejl: %1%
 Error_WordlistImportError=Der opstod en fejl under import af ordlisten: %1%
 Error_WordlistImportError=Der opstod en fejl under import af ordlisten: %1%
 Error_PwNotifyServiceError=Der opstod en fejl under k\u00f8rsel af tjenesten til notifikation om adgangskode: %1%
 Error_PwNotifyServiceError=Der opstod en fejl under k\u00f8rsel af tjenesten til notifikation om adgangskode: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_de.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Falsches Passwort. Versuchen Sie es erneut.
 Error_RecoverySequenceIncomplete=Fehler in der Sequenz f\u00fcr vergessene Passw\u00f6rter, versuchen Sie es erneut.
 Error_RecoverySequenceIncomplete=Fehler in der Sequenz f\u00fcr vergessene Passw\u00f6rter, versuchen Sie es erneut.
 Error_FileTypeIncorrect=Der Dateityp ist falsch.
 Error_FileTypeIncorrect=Der Dateityp ist falsch.
 Error_FileTooLarge=Die Datei ist zu gro\u00df.
 Error_FileTooLarge=Die Datei ist zu gro\u00df.
-Error_ClusterServiceError=Fehler beim Clusterservice: %1%. Weitere Informationen finden Sie in den Protokolldateien.
+Error_NodeServiceError=Fehler beim Clusterservice: %1%. Weitere Informationen finden Sie in den Protokolldateien.
 Error_RemoteErrorValue=Remotefehler: %1%
 Error_RemoteErrorValue=Remotefehler: %1%
 Error_WordlistImportError=Fehler beim Importieren der Wortliste: %1%
 Error_WordlistImportError=Fehler beim Importieren der Wortliste: %1%
 Error_PwNotifyServiceError=Fehler beim Ausf\u00fchren des Passwortbenachrichtigungsservice: %1%
 Error_PwNotifyServiceError=Fehler beim Ausf\u00fchren des Passwortbenachrichtigungsservice: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_en_CA.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Password incorrect. Please try again.
 Error_RecoverySequenceIncomplete=A problem occurred during the forgotten password sequence. Please try again.
 Error_RecoverySequenceIncomplete=A problem occurred during the forgotten password sequence. Please try again.
 Error_FileTypeIncorrect=The file type is not correct.
 Error_FileTypeIncorrect=The file type is not correct.
 Error_FileTooLarge=The file is too large.
 Error_FileTooLarge=The file is too large.
-Error_ClusterServiceError=An error occurred with the cluster service: %1%.   Check the log files for more information.
+Error_NodeServiceError=An error occurred with the node service: %1%.   Check the log files for more information.
 Error_RemoteErrorValue=Remote Error: %1%
 Error_RemoteErrorValue=Remote Error: %1%
 Error_WordlistImportError=An error occurred while importing the wordlist: %1%
 Error_WordlistImportError=An error occurred while importing the wordlist: %1%
 Error_PwNotifyServiceError=An error occurred while running the password notify service: %1%
 Error_PwNotifyServiceError=An error occurred while running the password notify service: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_es.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Contrase\u00f1a incorrecta. Int\u00e9ntelo de nuevo.
 Error_RecoverySequenceIncomplete=Se ha producido un problema durante la secuencia de contrase\u00f1a olvidada; int\u00e9ntelo de nuevo.
 Error_RecoverySequenceIncomplete=Se ha producido un problema durante la secuencia de contrase\u00f1a olvidada; int\u00e9ntelo de nuevo.
 Error_FileTypeIncorrect=El tipo de archivo es incorrecto.
 Error_FileTypeIncorrect=El tipo de archivo es incorrecto.
 Error_FileTooLarge=El archivo es demasiado grande.
 Error_FileTooLarge=El archivo es demasiado grande.
-Error_ClusterServiceError=Se ha producido un error en el servicio de cl\u00faster: %1%. Consulte los archivos de registro para obtener m\u00e1s informaci\u00f3n.
+Error_NodeServiceError=Se ha producido un error en el servicio de cl\u00faster: %1%. Consulte los archivos de registro para obtener m\u00e1s informaci\u00f3n.
 Error_RemoteErrorValue=Error remoto: %1%
 Error_RemoteErrorValue=Error remoto: %1%
 Error_WordlistImportError=Se ha producido un error al importar la lista de palabras: %1%
 Error_WordlistImportError=Se ha producido un error al importar la lista de palabras: %1%
 Error_PwNotifyServiceError=Se ha producido un error al ejecutar el servicio de notificaci\u00f3n de contrase\u00f1a: %1%
 Error_PwNotifyServiceError=Se ha producido un error al ejecutar el servicio de notificaci\u00f3n de contrase\u00f1a: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_fr.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Le mot de passe est incorrect. Veuillez r\u00e9essayer.
 Error_RecoverySequenceIncomplete=Un probl\u00e8me s'est produit lors de la s\u00e9quence de mot de passe oubli\u00e9, veuillez r\u00e9essayer.
 Error_RecoverySequenceIncomplete=Un probl\u00e8me s'est produit lors de la s\u00e9quence de mot de passe oubli\u00e9, veuillez r\u00e9essayer.
 Error_FileTypeIncorrect=Le type de fichier n'est pas correct.
 Error_FileTypeIncorrect=Le type de fichier n'est pas correct.
 Error_FileTooLarge=Le fichier est trop volumineux.
 Error_FileTooLarge=Le fichier est trop volumineux.
-Error_ClusterServiceError=Une erreur s'est produite avec le service de grappe : %1%. Consultez les fichiers journaux pour plus d'informations.
+Error_NodeServiceError=Une erreur s'est produite avec le service de grappe : %1%. Consultez les fichiers journaux pour plus d'informations.
 Error_RemoteErrorValue=Erreur distante : %1%
 Error_RemoteErrorValue=Erreur distante : %1%
 Error_WordlistImportError=Une erreur s'est produite lors de l'importation de la liste de mots : %1%
 Error_WordlistImportError=Une erreur s'est produite lors de l'importation de la liste de mots : %1%
 Error_PwNotifyServiceError=Une erreur s'est produite lors de l'ex\u00e9cution du service de notification de mot de passe : %1%
 Error_PwNotifyServiceError=Une erreur s'est produite lors de l'ex\u00e9cution du service de notification de mot de passe : %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_fr_CA.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Le mot de passe est incorrect. Veuillez r\u00e9essayer.
 Error_RecoverySequenceIncomplete=Un probl\u00e8me s'est produit lors de l'oubli du mot de passe, veuillez r\u00e9essayer.
 Error_RecoverySequenceIncomplete=Un probl\u00e8me s'est produit lors de l'oubli du mot de passe, veuillez r\u00e9essayer.
 Error_FileTypeIncorrect=Le type de fichier n'est pas correct.
 Error_FileTypeIncorrect=Le type de fichier n'est pas correct.
 Error_FileTooLarge=Le fichier est trop volumineux.
 Error_FileTooLarge=Le fichier est trop volumineux.
-Error_ClusterServiceError=Une erreur s'est produite avec le service de grappe\u00a0: %1%.   Consultez les fichiers journaux pour plus d'informations.
+Error_NodeServiceError=Une erreur s'est produite avec le service de grappe\u00a0: %1%.   Consultez les fichiers journaux pour plus d'informations.
 Error_RemoteErrorValue=Erreur distante : %1%
 Error_RemoteErrorValue=Erreur distante : %1%
 Error_WordlistImportError=Une erreur s'est produite lors de l'importation de la liste de mots\u00a0: %1%
 Error_WordlistImportError=Une erreur s'est produite lors de l'importation de la liste de mots\u00a0: %1%
 Error_PwNotifyServiceError=Une erreur s'est produite lors de l'ex\u00e9cution du service de notification de mot de passe\u00a0: %1%.
 Error_PwNotifyServiceError=Une erreur s'est produite lors de l'ex\u00e9cution du service de notification de mot de passe\u00a0: %1%.

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_it.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=La password \u00e8 errata. Riprovare.
 Error_RecoverySequenceIncomplete=Si \u00e8 verificato un problema durante le sequenza della password dimenticata. Riprovare.
 Error_RecoverySequenceIncomplete=Si \u00e8 verificato un problema durante le sequenza della password dimenticata. Riprovare.
 Error_FileTypeIncorrect=Il tipo di file non \u00e8 corretto.
 Error_FileTypeIncorrect=Il tipo di file non \u00e8 corretto.
 Error_FileTooLarge=Il file \u00e8 troppo grande.
 Error_FileTooLarge=Il file \u00e8 troppo grande.
-Error_ClusterServiceError=Si \u00e8 verificato un errore con il servizio del cluster: %1%.   Verificare il file di log per ulteriori informazioni.
+Error_NodeServiceError=Si \u00e8 verificato un errore con il servizio del cluster: %1%.   Verificare il file di log per ulteriori informazioni.
 Error_RemoteErrorValue=Errore remoto: %1%
 Error_RemoteErrorValue=Errore remoto: %1%
 Error_WordlistImportError=Si \u00e8 verificato un errore durante l'importazione dell'elenco di parole: %1%
 Error_WordlistImportError=Si \u00e8 verificato un errore durante l'importazione dell'elenco di parole: %1%
 Error_PwNotifyServiceError=Si \u00e8 verificato un errore durante il servizio di notifica della password: %1%
 Error_PwNotifyServiceError=Si \u00e8 verificato un errore durante il servizio di notifica della password: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_iw.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=\u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05d2\u05d5\u0
 Error_RecoverySequenceIncomplete=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05d1\u05e2\u05d9\u05d4 \u05d1\u05de\u05d4\u05dc\u05da \u05d4\u05e8\u05e6\u05e3 \u05e9\u05dc \u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05e0\u05e9\u05db\u05d7\u05d4. \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1.
 Error_RecoverySequenceIncomplete=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05d1\u05e2\u05d9\u05d4 \u05d1\u05de\u05d4\u05dc\u05da \u05d4\u05e8\u05e6\u05e3 \u05e9\u05dc \u05e1\u05d9\u05e1\u05de\u05d4 \u05e9\u05e0\u05e9\u05db\u05d7\u05d4. \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1.
 Error_FileTypeIncorrect=\u05e1\u05d5\u05d2 \u05d4\u05e7\u05d5\u05d1\u05e5 \u05e9\u05d2\u05d5\u05d9.
 Error_FileTypeIncorrect=\u05e1\u05d5\u05d2 \u05d4\u05e7\u05d5\u05d1\u05e5 \u05e9\u05d2\u05d5\u05d9.
 Error_FileTooLarge=\u05d4\u05e7\u05d5\u05d1\u05e5 \u05d2\u05d3\u05d5\u05dc \u05de\u05d3\u05d9.
 Error_FileTooLarge=\u05d4\u05e7\u05d5\u05d1\u05e5 \u05d2\u05d3\u05d5\u05dc \u05de\u05d3\u05d9.
-Error_ClusterServiceError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d0\u05e9\u05db\u05d5\u05dc\u05d5\u05ea:  %1%.  \u05d1\u05d3\u05d5\u05e7 \u05d0\u05ea \u05e7\u05d5\u05d1\u05e6\u05d9 \u05d9\u05d5\u05de\u05df \u05d4\u05e8\u05d9\u05e9\u05d5\u05dd \u05dc\u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3.
+Error_NodeServiceError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d0\u05e9\u05db\u05d5\u05dc\u05d5\u05ea:  %1%.  \u05d1\u05d3\u05d5\u05e7 \u05d0\u05ea \u05e7\u05d5\u05d1\u05e6\u05d9 \u05d9\u05d5\u05de\u05df \u05d4\u05e8\u05d9\u05e9\u05d5\u05dd \u05dc\u05e7\u05d1\u05dc\u05ea \u05de\u05d9\u05d3\u05e2 \u05e0\u05d5\u05e1\u05e3.
 Error_RemoteErrorValue=\u05e9\u05d2\u05d9\u05d0\u05d4 \u05de\u05e8\u05d5\u05d7\u05e7\u05ea: %1%
 Error_RemoteErrorValue=\u05e9\u05d2\u05d9\u05d0\u05d4 \u05de\u05e8\u05d5\u05d7\u05e7\u05ea: %1%
 Error_WordlistImportError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e2\u05ea \u05d9\u05d9\u05d1\u05d5\u05d0 \u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05de\u05d9\u05dc\u05d9\u05dd: %1%
 Error_WordlistImportError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05e2\u05ea \u05d9\u05d9\u05d1\u05d5\u05d0 \u05e8\u05e9\u05d9\u05de\u05ea \u05d4\u05de\u05d9\u05dc\u05d9\u05dd: %1%
 Error_PwNotifyServiceError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05de\u05d4\u05dc\u05da \u05d4\u05e4\u05e2\u05dc\u05ea \u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea  \u05e2\u05dc \u05e1\u05d9\u05e1\u05de\u05d0\u05d5\u05ea: %1%
 Error_PwNotifyServiceError=\u05d0\u05d9\u05e8\u05e2\u05d4 \u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05de\u05d4\u05dc\u05da \u05d4\u05e4\u05e2\u05dc\u05ea \u05e9\u05d9\u05e8\u05d5\u05ea \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea  \u05e2\u05dc \u05e1\u05d9\u05e1\u05de\u05d0\u05d5\u05ea: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_ja.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u9593\u9055\u3063\u30
 Error_RecoverySequenceIncomplete=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5fd8\u308c\u305f\u5834\u5408\u306e\u30b7\u30fc\u30b1\u30f3\u30b9\u3067\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 Error_RecoverySequenceIncomplete=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5fd8\u308c\u305f\u5834\u5408\u306e\u30b7\u30fc\u30b1\u30f3\u30b9\u3067\u554f\u984c\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u3082\u3046\u4e00\u5ea6\u3084\u308a\u76f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 Error_FileTypeIncorrect=\u30d5\u30a1\u30a4\u30eb\u306e\u7a2e\u985e\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
 Error_FileTypeIncorrect=\u30d5\u30a1\u30a4\u30eb\u306e\u7a2e\u985e\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
 Error_FileTooLarge=\u30d5\u30a1\u30a4\u30eb\u304c\u5927\u304d\u3059\u304e\u307e\u3059\u3002
 Error_FileTooLarge=\u30d5\u30a1\u30a4\u30eb\u304c\u5927\u304d\u3059\u304e\u307e\u3059\u3002
-Error_ClusterServiceError=\u30af\u30e9\u30b9\u30bf\u30b5\u30fc\u30d3\u30b9\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%\u3002\u8a73\u7d30\u306f\u3001\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+Error_NodeServiceError=\u30af\u30e9\u30b9\u30bf\u30b5\u30fc\u30d3\u30b9\u3067\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%\u3002\u8a73\u7d30\u306f\u3001\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002
 Error_RemoteErrorValue=\u30ea\u30e2\u30fc\u30c8\u30a8\u30e9\u30fc: %1%
 Error_RemoteErrorValue=\u30ea\u30e2\u30fc\u30c8\u30a8\u30e9\u30fc: %1%
 Error_WordlistImportError=\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u3092\u30a4\u30f3\u30dd\u30fc\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%
 Error_WordlistImportError=\u30ef\u30fc\u30c9\u30ea\u30b9\u30c8\u3092\u30a4\u30f3\u30dd\u30fc\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%
 Error_PwNotifyServiceError=\u30d1\u30b9\u30ef\u30fc\u30c9\u901a\u77e5\u30b5\u30fc\u30d3\u30b9\u3092\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%
 Error_PwNotifyServiceError=\u30d1\u30b9\u30ef\u30fc\u30c9\u901a\u77e5\u30b5\u30fc\u30d3\u30b9\u3092\u5b9f\u884c\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_nl.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Het wachtwoord is onjuist. Probeer het opnieuw.
 Error_RecoverySequenceIncomplete=Er is een fout opgetreden tijdens de vergeten wachtwoordreeks. Probeer het opnieuw.
 Error_RecoverySequenceIncomplete=Er is een fout opgetreden tijdens de vergeten wachtwoordreeks. Probeer het opnieuw.
 Error_FileTypeIncorrect=Het bestandstype is niet correct.
 Error_FileTypeIncorrect=Het bestandstype is niet correct.
 Error_FileTooLarge=Het bestand is te groot.
 Error_FileTooLarge=Het bestand is te groot.
-Error_ClusterServiceError=Er is een fout opgetreden voor de clusterservice: %1%. Raadpleeg de logbestanden voor meer informatie.
+Error_NodeServiceError=Er is een fout opgetreden voor de clusterservice: %1%. Raadpleeg de logbestanden voor meer informatie.
 Error_RemoteErrorValue=Externe fout: %1%
 Error_RemoteErrorValue=Externe fout: %1%
 Error_WordlistImportError=Er is een fout opgetreden bij het importeren van de woordenlijst: %1%
 Error_WordlistImportError=Er is een fout opgetreden bij het importeren van de woordenlijst: %1%
 Error_PwNotifyServiceError=Er is een fout opgetreden tijdens het uitvoeren van de wachtwoordwaarschuwingsservice: %1%
 Error_PwNotifyServiceError=Er is een fout opgetreden tijdens het uitvoeren van de wachtwoordwaarschuwingsservice: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_pl.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Nieprawid\u0142owe has\u0142o. Spr\u00f3buj ponownie.
 Error_RecoverySequenceIncomplete=Wyst\u0105pi\u0142 problem podczas sekwencji zapomnianego has\u0142a. Spr\u00f3buj ponownie.
 Error_RecoverySequenceIncomplete=Wyst\u0105pi\u0142 problem podczas sekwencji zapomnianego has\u0142a. Spr\u00f3buj ponownie.
 Error_FileTypeIncorrect=Typ pliku nie jest poprawny.
 Error_FileTypeIncorrect=Typ pliku nie jest poprawny.
 Error_FileTooLarge=Plik jest zbyt du\u017cy.
 Error_FileTooLarge=Plik jest zbyt du\u017cy.
-Error_ClusterServiceError=Wyst\u0105pi\u0142 b\u0142\u0105d w us\u0142udze klastra: %1%. Sprawd\u017a pliki dziennika, aby uzyska\u0107 wi\u0119cej informacji.
+Error_NodeServiceError=Wyst\u0105pi\u0142 b\u0142\u0105d w us\u0142udze klastra: %1%. Sprawd\u017a pliki dziennika, aby uzyska\u0107 wi\u0119cej informacji.
 Error_RemoteErrorValue=B\u0142\u0105d zdalny: %1%
 Error_RemoteErrorValue=B\u0142\u0105d zdalny: %1%
 Error_WordlistImportError=Wyst\u0105pi\u0142 b\u0142\u0105d podczas importowania listy s\u0142\u00f3w: %1%
 Error_WordlistImportError=Wyst\u0105pi\u0142 b\u0142\u0105d podczas importowania listy s\u0142\u00f3w: %1%
 Error_PwNotifyServiceError=Wyst\u0105pi\u0142 b\u0142\u0105d podczas uruchamiania us\u0142ugi powiadamiania o ha\u015ble: %1%
 Error_PwNotifyServiceError=Wyst\u0105pi\u0142 b\u0142\u0105d podczas uruchamiania us\u0142ugi powiadamiania o ha\u015ble: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_pt_BR.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=Senha incorreta, tente novamente.
 Error_RecoverySequenceIncomplete=Houve um problema durante a sequ\u00eancia de senha esquecida, tente novamente.
 Error_RecoverySequenceIncomplete=Houve um problema durante a sequ\u00eancia de senha esquecida, tente novamente.
 Error_FileTypeIncorrect=O tipo de arquivo n\u00e3o est\u00e1 correto.
 Error_FileTypeIncorrect=O tipo de arquivo n\u00e3o est\u00e1 correto.
 Error_FileTooLarge=O arquivo \u00e9 grande demais.
 Error_FileTooLarge=O arquivo \u00e9 grande demais.
-Error_ClusterServiceError=Erro no servi\u00e7o de cluster: %1%. Verifique os arquivos de registro para obter mais informa\u00e7\u00f5es.
+Error_NodeServiceError=Erro no servi\u00e7o de cluster: %1%. Verifique os arquivos de registro para obter mais informa\u00e7\u00f5es.
 Error_RemoteErrorValue=Erro Remoto: %1%
 Error_RemoteErrorValue=Erro Remoto: %1%
 Error_WordlistImportError=Erro ao importar a lista de palavras: %1%
 Error_WordlistImportError=Erro ao importar a lista de palavras: %1%
 Error_PwNotifyServiceError=Erro ao executar o servi\u00e7o de notifica\u00e7\u00e3o de senha: %1%
 Error_PwNotifyServiceError=Erro ao executar o servi\u00e7o de notifica\u00e7\u00e3o de senha: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_ru.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=\u041f\u0430\u0440\u043e\u043b\u044c \u043d\u0435\u043f\u0
 Error_RecoverySequenceIncomplete=\u041f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e \u043f\u043e\u0432\u043e\u0434\u0443 \u0437\u0430\u0431\u044b\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u043e\u043b\u044f \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.
 Error_RecoverySequenceIncomplete=\u041f\u0440\u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0438 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u043f\u043e \u043f\u043e\u0432\u043e\u0434\u0443 \u0437\u0430\u0431\u044b\u0442\u043e\u0433\u043e \u043f\u0430\u0440\u043e\u043b\u044f \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430. \u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.
 Error_FileTypeIncorrect=\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0442\u0438\u043f \u0444\u0430\u0439\u043b\u0430.
 Error_FileTypeIncorrect=\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0442\u0438\u043f \u0444\u0430\u0439\u043b\u0430.
 Error_FileTooLarge=\u0424\u0430\u0439\u043b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043e\u043b\u044c\u0448\u043e\u0439.
 Error_FileTooLarge=\u0424\u0430\u0439\u043b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u043e\u043b\u044c\u0448\u043e\u0439.
-Error_ClusterServiceError=\u0412 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%. \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0441\u043c. \u0432 \u0444\u0430\u0439\u043b\u0430\u0445 \u0436\u0443\u0440\u043d\u0430\u043b\u0430.
+Error_NodeServiceError=\u0412 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%. \u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0441\u043c. \u0432 \u0444\u0430\u0439\u043b\u0430\u0445 \u0436\u0443\u0440\u043d\u0430\u043b\u0430.
 Error_RemoteErrorValue=\u0423\u0434\u0430\u043b\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: %1%
 Error_RemoteErrorValue=\u0423\u0434\u0430\u043b\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: %1%
 Error_WordlistImportError=\u041f\u0440\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043b\u043e\u0432 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%
 Error_WordlistImportError=\u041f\u0440\u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u0441\u043b\u043e\u0432 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%
 Error_PwNotifyServiceError=\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e \u043f\u0430\u0440\u043e\u043b\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%
 Error_PwNotifyServiceError=\u041f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0441\u043b\u0443\u0436\u0431\u044b \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e \u043f\u0430\u0440\u043e\u043b\u0435 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_sv.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=L\u00f6senordet \u00e4r felaktigt. F\u00f6rs\u00f6k igen.
 Error_RecoverySequenceIncomplete=Ett fel intr\u00e4ffade under sekvensen f\u00f6r gl\u00f6mt l\u00f6senord. F\u00f6rs\u00f6k igen.
 Error_RecoverySequenceIncomplete=Ett fel intr\u00e4ffade under sekvensen f\u00f6r gl\u00f6mt l\u00f6senord. F\u00f6rs\u00f6k igen.
 Error_FileTypeIncorrect=Filtypen \u00e4r felaktig.
 Error_FileTypeIncorrect=Filtypen \u00e4r felaktig.
 Error_FileTooLarge=Filen \u00e4r f\u00f6r stor.
 Error_FileTooLarge=Filen \u00e4r f\u00f6r stor.
-Error_ClusterServiceError=Ett fel intr\u00e4ffade med klustertj\u00e4nsten: %1%. Se loggfilerna f\u00f6r mer information.
+Error_NodeServiceError=Ett fel intr\u00e4ffade med klustertj\u00e4nsten: %1%. Se loggfilerna f\u00f6r mer information.
 Error_RemoteErrorValue=Fj\u00e4rrfel: %1%
 Error_RemoteErrorValue=Fj\u00e4rrfel: %1%
 Error_WordlistImportError=Ett fel intr\u00e4ffade n\u00e4r ordlistan skulle importeras: %1%
 Error_WordlistImportError=Ett fel intr\u00e4ffade n\u00e4r ordlistan skulle importeras: %1%
 Error_PwNotifyServiceError=Ett fel intr\u00e4ffade n\u00e4r aviseringstj\u00e4nsten f\u00f6r l\u00f6senord k\u00f6rdes: %1%
 Error_PwNotifyServiceError=Ett fel intr\u00e4ffade n\u00e4r aviseringstj\u00e4nsten f\u00f6r l\u00f6senord k\u00f6rdes: %1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_zh_CN.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=\u53e3\u4ee4\u4e0d\u6b63\u786e\uff0c\u8bf7\u91cd\u8bd5\u30
 Error_RecoverySequenceIncomplete=\u5fd8\u8bb0\u53e3\u4ee4\u64cd\u4f5c\u987a\u5e8f\u4e2d\u51fa\u9519\uff0c\u8bf7\u91cd\u8bd5\u3002
 Error_RecoverySequenceIncomplete=\u5fd8\u8bb0\u53e3\u4ee4\u64cd\u4f5c\u987a\u5e8f\u4e2d\u51fa\u9519\uff0c\u8bf7\u91cd\u8bd5\u3002
 Error_FileTypeIncorrect=\u6587\u4ef6\u7c7b\u578b\u9519\u8bef\u3002
 Error_FileTypeIncorrect=\u6587\u4ef6\u7c7b\u578b\u9519\u8bef\u3002
 Error_FileTooLarge=\u6587\u4ef6\u8fc7\u5927\u3002
 Error_FileTooLarge=\u6587\u4ef6\u8fc7\u5927\u3002
-Error_ClusterServiceError=\u7fa4\u96c6\u670d\u52a1\u51fa\u9519\uff1a%1%\u3002\u8bf7\u67e5\u770b\u65e5\u5fd7\u6587\u4ef6\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002
+Error_NodeServiceError=\u7fa4\u96c6\u670d\u52a1\u51fa\u9519\uff1a%1%\u3002\u8bf7\u67e5\u770b\u65e5\u5fd7\u6587\u4ef6\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002
 Error_RemoteErrorValue=\u8fdc\u7a0b\u9519\u8bef\uff1a%1%
 Error_RemoteErrorValue=\u8fdc\u7a0b\u9519\u8bef\uff1a%1%
 Error_WordlistImportError=\u5bfc\u5165\u5355\u8bcd\u8868\u65f6\u51fa\u9519\uff1a%1%
 Error_WordlistImportError=\u5bfc\u5165\u5355\u8bcd\u8868\u65f6\u51fa\u9519\uff1a%1%
 Error_PwNotifyServiceError=\u8fd0\u884c\u53e3\u4ee4\u901a\u77e5\u670d\u52a1\u65f6\u51fa\u9519\uff1a%1%
 Error_PwNotifyServiceError=\u8fd0\u884c\u53e3\u4ee4\u901a\u77e5\u670d\u52a1\u65f6\u51fa\u9519\uff1a%1%

+ 1 - 1
server/src/main/resources/password/pwm/i18n/Error_zh_TW.properties

@@ -162,7 +162,7 @@ Error_PasswordOnlyBad=\u5bc6\u78bc\u4e0d\u6b63\u78ba\uff0c\u8acb\u518d\u8a66\u4e
 Error_RecoverySequenceIncomplete=\u5fd8\u8a18\u5bc6\u78bc\u5e8f\u5217\u6642\u767c\u751f\u554f\u984c\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002
 Error_RecoverySequenceIncomplete=\u5fd8\u8a18\u5bc6\u78bc\u5e8f\u5217\u6642\u767c\u751f\u554f\u984c\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002
 Error_FileTypeIncorrect=\u6a94\u6848\u985e\u578b\u4e0d\u6b63\u78ba\u3002
 Error_FileTypeIncorrect=\u6a94\u6848\u985e\u578b\u4e0d\u6b63\u78ba\u3002
 Error_FileTooLarge=\u6a94\u6848\u592a\u5927\u3002
 Error_FileTooLarge=\u6a94\u6848\u592a\u5927\u3002
-Error_ClusterServiceError=\u53e2\u96c6\u670d\u52d9\u767c\u751f\u932f\u8aa4\uff1a%1%\u3002\u8acb\u67e5\u770b\u8a18\u9304\u6a94\u4ee5\u53d6\u5f97\u66f4\u591a\u8cc7\u8a0a\u3002
+Error_NodeServiceError=\u53e2\u96c6\u670d\u52d9\u767c\u751f\u932f\u8aa4\uff1a%1%\u3002\u8acb\u67e5\u770b\u8a18\u9304\u6a94\u4ee5\u53d6\u5f97\u66f4\u591a\u8cc7\u8a0a\u3002
 Error_RemoteErrorValue=\u9060\u7aef\u932f\u8aa4\uff1a%1%
 Error_RemoteErrorValue=\u9060\u7aef\u932f\u8aa4\uff1a%1%
 Error_WordlistImportError=\u8f38\u5165\u55ae\u5b57\u6e05\u55ae\u6642\u767c\u751f\u932f\u8aa4\uff1a%1%
 Error_WordlistImportError=\u8f38\u5165\u55ae\u5b57\u6e05\u55ae\u6642\u767c\u751f\u932f\u8aa4\uff1a%1%
 Error_PwNotifyServiceError=\u57f7\u884c\u5bc6\u78bc\u901a\u77e5\u670d\u52d9\u767c\u751f\u932f\u8aa4\uff1a%1%
 Error_PwNotifyServiceError=\u57f7\u884c\u5bc6\u78bc\u901a\u77e5\u670d\u52d9\u767c\u751f\u932f\u8aa4\uff1a%1%

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

@@ -110,6 +110,7 @@ Rule_MinimumUpperCase=Minimum Upper Case
 Rule_MaximumUpperCase=Maximum Upper Case
 Rule_MaximumUpperCase=Maximum Upper Case
 Rule_MinimumLowerCase=Minimum Lower Case
 Rule_MinimumLowerCase=Minimum Lower Case
 Rule_MaximumLowerCase=Maximum Lower Case
 Rule_MaximumLowerCase=Maximum Lower Case
+Rule_AllowNonAlpha=Allow Non-Alpha
 Rule_AllowNumeric=Allow Numeric
 Rule_AllowNumeric=Allow Numeric
 Rule_MinimumNumeric=Minimum Numeric
 Rule_MinimumNumeric=Minimum Numeric
 Rule_MaximumNumeric=Maximum Numeric
 Rule_MaximumNumeric=Maximum Numeric

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

@@ -535,6 +535,7 @@ Setting_Description_password.policy.allowFirstCharNumeric=Enable this option to
 Setting_Description_password.policy.allowFirstCharSpecial=Enable this option to allow the first character of the password to be a special character.  Applies only if the password policy allows special characters.
 Setting_Description_password.policy.allowFirstCharSpecial=Enable this option to allow the first character of the password to be a special character.  Applies only if the password policy allows special characters.
 Setting_Description_password.policy.allowLastCharNumeric=Enable this option to allow the last character of the password to be numeric.  Applies only if the password policy allows numeric characters.
 Setting_Description_password.policy.allowLastCharNumeric=Enable this option to allow the last character of the password to be numeric.  Applies only if the password policy allows numeric characters.
 Setting_Description_password.policy.allowLastCharSpecial=Enable this option to allow the last character of the password to be a special character.  Applies only if the password policy allows special characters.
 Setting_Description_password.policy.allowLastCharSpecial=Enable this option to allow the last character of the password to be a special character.  Applies only if the password policy allows special characters.
+Setting_Description_password.policy.allowNonAlpha=Enable this option to allow non-alphabetic characters in the password.
 Setting_Description_password.policy.allowNumeric=Enable this option to allow numeric characters in the password.
 Setting_Description_password.policy.allowNumeric=Enable this option to allow numeric characters in the password.
 Setting_Description_password.policy.allowSpecial=Enable this option to allow special (non alpha-numeric) characters in the password.
 Setting_Description_password.policy.allowSpecial=Enable this option to allow special (non alpha-numeric) characters in the password.
 Setting_Description_password.policy.caseSensitivity=Enable this option to control if the password is case sensitive.  In most cases, @PwmAppName@ can read this from the directory, but in some cases, the system cannot correctly read this value, so you can override it here.
 Setting_Description_password.policy.caseSensitivity=Enable this option to control if the password is case sensitive.  In most cases, @PwmAppName@ can read this from the directory, but in some cases, the system cannot correctly read this value, so you can override it here.
@@ -1054,6 +1055,7 @@ Setting_Label_password.policy.allowFirstCharNumeric=Allow First Character Numeri
 Setting_Label_password.policy.allowFirstCharSpecial=Allow First Character Special
 Setting_Label_password.policy.allowFirstCharSpecial=Allow First Character Special
 Setting_Label_password.policy.allowLastCharNumeric=Allow Last Character Numeric
 Setting_Label_password.policy.allowLastCharNumeric=Allow Last Character Numeric
 Setting_Label_password.policy.allowLastCharSpecial=Allow Last Character Special
 Setting_Label_password.policy.allowLastCharSpecial=Allow Last Character Special
+Setting_Label_password.policy.allowNonAlpha=Allow Non-Alphabetic Characters
 Setting_Label_password.policy.allowNumeric=Allow Numeric Characters
 Setting_Label_password.policy.allowNumeric=Allow Numeric Characters
 Setting_Label_password.policy.allowSpecial=Allow Special Characters
 Setting_Label_password.policy.allowSpecial=Allow Special Characters
 Setting_Label_password.policy.caseSensitivity=Password is Case Sensitive
 Setting_Label_password.policy.caseSensitivity=Password is Case Sensitive

+ 42 - 0
server/src/test/java/password/pwm/config/profile/PwmPasswordRuleTest.java

@@ -0,0 +1,42 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.config.profile;
+
+import org.junit.Test;
+import password.pwm.PwmConstants;
+
+public class PwmPasswordRuleTest
+{
+    @Test
+    public void testRuleLabels() throws Exception
+    {
+        for ( final PwmPasswordRule rule : PwmPasswordRule.values() )
+        {
+            final String value = rule.getLabel( PwmConstants.DEFAULT_LOCALE, null );
+            if ( value == null || value.contains( "MissingKey" ) )
+            {
+                throw new Exception(" missing label for PwmPasswordRule " + rule.name() );
+            }
+        }
+    }
+}

+ 37 - 0
webapp/src/main/webapp/WEB-INF/jsp/admin-user-debug.jsp

@@ -33,6 +33,8 @@
 <%@ page import="java.util.Map" %>
 <%@ page import="java.util.Map" %>
 <%@ page import="password.pwm.util.java.TimeDuration" %>
 <%@ page import="password.pwm.util.java.TimeDuration" %>
 <%@ page import="password.pwm.util.i18n.LocaleHelper" %>
 <%@ page import="password.pwm.util.i18n.LocaleHelper" %>
+<%@ page import="password.pwm.config.PwmSetting" %>
+<%@ page import="password.pwm.svc.PwmService" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
 <%@ taglib uri="pwm" prefix="pwm" %>
@@ -248,6 +250,41 @@
             </tr>
             </tr>
         </table>
         </table>
         <br/>
         <br/>
+
+        <% if (JspUtility.getPwmRequest( pageContext ).getConfig().readSettingAsBoolean( PwmSetting.PW_EXPY_NOTIFY_ENABLE ) ) { %>
+        <table>
+            <tr>
+                <td colspan="10" class="title">Password Notification Status</td>
+            </tr>
+            <% if ( userDebugDataBean.getPwNotifyUserStatus() == null ) { %>
+            <tr>
+                <td class="key">Last Notification Sent</td>
+                <td><pwm:display key="<%=Display.Value_NotApplicable.toString()%>"/></td>
+            </tr>
+            <% } else { %>
+            <tr>
+                <td class="key">Last Notification Sent</td>
+                <td>
+                    <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getPwNotifyUserStatus().getLastNotice())%>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">Last Notification Password Expiration Time</td>
+                <td>
+                    <%=JspUtility.friendlyWrite(pageContext, userDebugDataBean.getPwNotifyUserStatus().getExpireTime())%>
+                </td>
+            </tr>
+            <tr>
+                <td class="key">Last Notification Interval</td>
+                <td>
+                    <%=userDebugDataBean.getPwNotifyUserStatus().getInterval()%>
+                </td>
+            </tr>
+            <% } %>
+        </table>
+        <br/>
+        <% } %>
+
         <table>
         <table>
             <tr>
             <tr>
                 <td colspan="10" class="title">Applied Configuration</td>
                 <td colspan="10" class="title">Applied Configuration</td>

+ 1 - 1
webapp/src/main/webapp/public/health.jsp

@@ -158,7 +158,7 @@
         }
         }
 
 
         function handleWarnFlash() {
         function handleWarnFlash() {
-            if (PWM_GLOBAL['pwm-health'] == "WARN") {
+            if (PWM_GLOBAL['pwm-health'] === "WARN") {
                 PWM_MAIN.flashDomElement(errorColor,'body',3000);
                 PWM_MAIN.flashDomElement(errorColor,'body',3000);
             }
             }
         }
         }