Explorar o código

refactoring and logging improvements

Jason Rivard %!s(int64=2) %!d(string=hai) anos
pai
achega
4626393bc6
Modificáronse 67 ficheiros con 1104 adicións e 1221 borrados
  1. 34 0
      lib-util/src/main/java/password/pwm/util/java/JavaHelper.java
  2. 1 4
      server/src/main/java/password/pwm/PwmAboutProperty.java
  3. 1 1
      server/src/main/java/password/pwm/PwmApplication.java
  4. 1 0
      server/src/main/java/password/pwm/PwmConstants.java
  5. 1 1
      server/src/main/java/password/pwm/bean/SessionLabel.java
  6. 4 2
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  7. 2 2
      server/src/main/java/password/pwm/config/profile/PwmPasswordPolicy.java
  8. 3 2
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  9. 3 2
      server/src/main/java/password/pwm/config/stored/StoredConfigXmlSerializer.java
  10. 1 1
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  11. 1 0
      server/src/main/java/password/pwm/http/PwmRequestAttribute.java
  12. 59 25
      server/src/main/java/password/pwm/http/PwmURL.java
  13. 2 2
      server/src/main/java/password/pwm/http/filter/AuthenticationFilter.java
  14. 37 27
      server/src/main/java/password/pwm/http/filter/SessionFilter.java
  15. 1 1
      server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  16. 1 1
      server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  17. 3 3
      server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java
  18. 1 1
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  19. 1 1
      server/src/main/java/password/pwm/http/servlet/admin/system/ConfigManagerWordlistServlet.java
  20. 1 1
      server/src/main/java/password/pwm/http/servlet/changepw/ChangePasswordServlet.java
  21. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/function/UserMatchViewerFunction.java
  22. 3 3
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java
  23. 2 2
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskDetailInfoBean.java
  24. 2 2
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  25. 1 1
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  26. 5 1
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java
  27. 11 27
      server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java
  28. 24 101
      server/src/main/java/password/pwm/http/tag/DisplayTag.java
  29. 35 57
      server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java
  30. 16 27
      server/src/main/java/password/pwm/http/tag/PasswordChangeMessageTag.java
  31. 35 526
      server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java
  32. 44 0
      server/src/main/java/password/pwm/http/tag/PwmAbstractTag.java
  33. 19 18
      server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java
  34. 12 21
      server/src/main/java/password/pwm/http/tag/PwmContextTag.java
  35. 14 51
      server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java
  36. 12 25
      server/src/main/java/password/pwm/http/tag/PwmMacroTag.java
  37. 20 23
      server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java
  38. 14 28
      server/src/main/java/password/pwm/http/tag/PwmTabIndexTag.java
  39. 13 30
      server/src/main/java/password/pwm/http/tag/PwmTextFileTag.java
  40. 21 33
      server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java
  41. 21 21
      server/src/main/java/password/pwm/http/tag/UserInfoTag.java
  42. 3 3
      server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java
  43. 13 42
      server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java
  44. 1 1
      server/src/main/java/password/pwm/ldap/LdapDomainService.java
  45. 4 4
      server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java
  46. 1 1
      server/src/main/java/password/pwm/ldap/auth/SimpleLdapAuthenticator.java
  47. 1 1
      server/src/main/java/password/pwm/svc/email/EmailService.java
  48. 4 9
      server/src/main/java/password/pwm/svc/httpclient/ApachePwmHttpClient.java
  49. 5 9
      server/src/main/java/password/pwm/svc/httpclient/JavaPwmHttpClient.java
  50. 7 9
      server/src/main/java/password/pwm/svc/node/NodeMachine.java
  51. 7 2
      server/src/main/java/password/pwm/svc/node/NodeService.java
  52. 2 2
      server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java
  53. 2 2
      server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java
  54. 3 2
      server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java
  55. 2 2
      server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  56. 7 5
      server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java
  57. 18 9
      server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java
  58. 2 2
      server/src/main/java/password/pwm/user/UserInfoBean.java
  59. 18 6
      server/src/main/java/password/pwm/util/logging/PwmLogManager.java
  60. 3 11
      server/src/main/java/password/pwm/util/logging/PwmLogUtil.java
  61. 0 10
      server/src/main/java/password/pwm/util/logging/PwmLogger.java
  62. 504 0
      server/src/main/java/password/pwm/util/password/PasswordRequirementViewableRuleGenerator.java
  63. 3 3
      server/src/main/java/password/pwm/util/password/PasswordRuleChecks.java
  64. 3 3
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  65. 4 4
      server/src/main/java/password/pwm/util/password/RandomGeneratorConfig.java
  66. 3 3
      server/src/main/java/password/pwm/util/password/RandomPasswordGenerator.java
  67. 1 1
      server/src/test/java/password/pwm/config/stored/ConfigurationCleanerTest.java

+ 34 - 0
lib-util/src/main/java/password/pwm/util/java/JavaHelper.java

@@ -36,21 +36,26 @@ import java.io.Writer;
 import java.lang.management.LockInfo;
 import java.lang.management.MonitorInfo;
 import java.lang.management.ThreadInfo;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HexFormat;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.concurrent.atomic.LongAccumulator;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
@@ -496,4 +501,33 @@ public final class JavaHelper
         final long next = input + 1;
         return next > 0 ? next : 0;
     }
+    
+    public static <T> List<T> instancesOfSealedInterface( final Class<T> sealedInterface )
+    {
+        if ( !Objects.requireNonNull( sealedInterface ).isSealed() )
+        {
+            throw new IllegalArgumentException( "sealedInterface argument is required to be marked as sealed" );
+        }
+
+        final Function<Class<T>, T> f = theClass ->
+        {
+            try
+            {
+                final Constructor<T> constructor = theClass.getDeclaredConstructor();
+                constructor.setAccessible( true );
+                return ( T ) constructor.newInstance();
+            }
+            catch ( final InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e )
+            {
+                throw new RuntimeException( e );
+            }
+        };
+
+        final List<T> list = new ArrayList<>();
+        for ( final Class<?> loopClass : sealedInterface.getPermittedSubclasses() )
+        {
+            list.add( f.apply( (Class<T> ) loopClass ) );
+        }
+        return List.copyOf( list );
+    }
 }

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

@@ -160,11 +160,8 @@ public enum PwmAboutProperty
         {
             return date.toString();
         }
-        else
-        {
-            return LocaleHelper.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, Display.Value_NotApplicable, null );
-        }
 
+        return LocaleHelper.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, Display.Value_NotApplicable, null );
     }
 
     public String getLabel( )

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

@@ -301,7 +301,7 @@ public class PwmApplication
 
         MBeanUtility.registerMBean( this );
 
-        UserAgentUtils.initializeCache();
+        UserAgentUtils.initializeCache( sessionLabel );
 
         LOGGER.trace( sessionLabel, () -> "completed post init tasks", TimeDuration.fromCurrent( startTime ) );
     }

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

@@ -151,6 +151,7 @@ public abstract class PwmConstants
 
     public static final String VALUE_REPLACEMENT_USERNAME = "%USERNAME%";
 
+    public static final String LOGBACK_APP_PATH_FILENAME = "logback.xml";
     public static final String RESOURCE_FILE_EULA_TXT = "eula.txt";
     public static final String RESOURCE_FILE_PRIVACY_TXT = "privacy.txt";
     public static final String RESOURCE_FILE_WELCOME_TXT = "welcome.txt";

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

@@ -168,7 +168,7 @@ public class SessionLabel
                 }
                 catch ( final PwmUnrecoverableException e )
                 {
-                    LOGGER.error( () -> "unexpected error reading username: " + e.getMessage(), e );
+                    LOGGER.error( () -> "unexpected error reading username while building SessionLabel: " + e.getMessage(), e );
                 }
             }
         }

+ 4 - 2
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -205,13 +205,15 @@ public class LdapProfile extends AbstractProfile implements Profile
 
                 {
                     final String finalCanonical = canonicalValue;
-                    LOGGER.trace( () -> "read and cached canonical ldap DN value for input '" + dnValue + "' as '" + finalCanonical + "'",
+                    LOGGER.trace( sessionLabel, () -> "read and cached canonical ldap DN value for input '"
+                                    + dnValue + "' as '" + finalCanonical + "'",
                             TimeDuration.fromCurrent( startTime ) );
                 }
             }
             catch ( final ChaiUnavailableException | ChaiOperationException e )
             {
-                LOGGER.error( () -> "error while reading canonicalDN for dn value '" + dnValue + "', error: " + e.getMessage() );
+                LOGGER.error( sessionLabel, () -> "error while reading canonicalDN for dn value '"
+                        + dnValue + "', error: " + e.getMessage() );
                 return dnValue;
             }
         }

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

@@ -239,7 +239,7 @@ public class PwmPasswordPolicy implements Profile
         return chaiPasswordPolicy;
     }
 
-    public PasswordRuleReaderHelper getRuleHelper( )
+    public PasswordRuleReaderHelper ruleHelper( )
     {
         return new PasswordRuleReaderHelper( this );
     }
@@ -341,7 +341,7 @@ public class PwmPasswordPolicy implements Profile
         final Locale locale = PwmConstants.DEFAULT_LOCALE;
         final PolicyMetaData policyMetaData = pwmPasswordPolicy.getPolicyMetaData();
 
-        final PasswordRuleReaderHelper ruleHelper = pwmPasswordPolicy.getRuleHelper();
+        final PasswordRuleReaderHelper ruleHelper = pwmPasswordPolicy.ruleHelper();
         final List<HealthRecord> returnList = new ArrayList<>();
         final Map<PwmPasswordRule, PwmPasswordRule> rulePairs = new LinkedHashMap<>();
         rulePairs.put( PwmPasswordRule.MinimumLength, PwmPasswordRule.MaximumLength );

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

@@ -101,7 +101,7 @@ public class ConfigurationCleaner
 
             final Optional<ProfileID> profileID = key.getProfileID();
 
-            LOGGER.info( () -> "converting deprecated non-default setting "
+            LOGGER.info( SESSION_LABEL, () -> "converting deprecated non-default setting "
                     + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
                     + " to replacement setting "
                     + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value="
@@ -215,7 +215,8 @@ public class ConfigurationCleaner
 
             for ( final ProfileID destProfile : targetProfiles )
             {
-                LOGGER.info( () -> "moving setting " + key + " without profile attribute to profile \"" + destProfile + "\"." );
+                LOGGER.info( SESSION_LABEL, () -> "moving setting " + key
+                        + " without profile attribute to profile \"" + destProfile + "\"." );
                 {
                     try
                     {

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

@@ -752,7 +752,7 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                             newValueElement.setText( textValue.get().trim() );
                             settingElement.attachElement( newValueElement );
                             final String key = settingElement.getAttribute( StoredConfigXmlConstants.XML_ATTRIBUTE_KEY ).orElse( "" );
-                            LOGGER.info( () -> "migrating pre-xml 'value' tag format to use value element for key: " + key );
+                            LOGGER.info( SESSION_LABEL, () -> "migrating pre-xml 'value' tag format to use value element for key: " + key );
                         }
                     }
                 }
@@ -882,7 +882,8 @@ public class StoredConfigXmlSerializer implements StoredConfigSerializer
                         final Optional<String> value = property.getText();
                         if ( key.isPresent() && value.isPresent() )
                         {
-                            LOGGER.info( () -> "migrating app-property config element '" + key.get() + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() );
+                            LOGGER.info( SESSION_LABEL, () -> "migrating app-property config element '" + key.get()
+                                    + "' to setting " + PwmSetting.APP_PROPERTY_OVERRIDES.getKey() );
                             final String newValue = key.get() + "=" + value.get();
 
                             final List<String> existingValues = new ArrayList<>();

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

@@ -318,7 +318,7 @@ public class LDAPHealthChecker implements HealthSupplier
                             pwmDomain, sessionLabel, userIdentity, theUser );
 
                     boolean doPasswordChange = true;
-                    final int minLifetimeSeconds = passwordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
+                    final int minLifetimeSeconds = passwordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
                     if ( minLifetimeSeconds > 0 )
                     {
                         final Instant pwdLastModified = PasswordUtility.determinePwdLastModified(

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

@@ -108,6 +108,7 @@ public enum PwmRequestAttribute
     ExternalResponseInstructions,
 
     JspIndexTabCounter,
+    JspAutofocusStatus,
 
     GoBackAction,;
 }

+ 59 - 25
server/src/main/java/password/pwm/http/PwmURL.java

@@ -26,6 +26,7 @@ import password.pwm.config.AppConfig;
 import password.pwm.error.PwmInternalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.servlet.PwmServletDefinition;
+import password.pwm.util.java.EnumUtil;
 import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.json.JsonFactory;
@@ -56,6 +57,38 @@ public class PwmURL
     private final Supplier<Optional<PwmServletDefinition>> pwmServletDefinition = LazySupplier.create(
             () -> getServletDefinitionImpl( this ) );
 
+    public enum Scheme
+    {
+        http( 80 ),
+        https( 443 ),
+        file( -1 ),
+        ldap( 389 ),
+        ldaps( 636 ),;
+
+        private final int defaultPort;
+
+        Scheme( final int defaultPort )
+        {
+            this.defaultPort = defaultPort;
+        }
+
+        public int getDefaultPort()
+        {
+            return defaultPort;
+        }
+
+        public static Optional<Scheme> fromUri( final URI uri )
+        {
+            if ( uri == null )
+            {
+                return Optional.empty();
+            }
+
+            return EnumUtil.readEnumFromPredicate(
+                    Scheme.class, ( loopUri ) -> loopUri.name().equals( uri.getScheme() ) );
+        }
+    }
+
     private PwmURL(
             final URI uri,
             final String contextPath,
@@ -382,35 +415,11 @@ public class PwmURL
         final int port = uri.getPort();
         if ( port < 1 )
         {
-            return portForUriScheme( uri.getScheme() );
+            return Scheme.fromUri( uri ).map( Scheme::getDefaultPort ).orElse( -1 );
         }
         return port;
     }
 
-    private static int portForUriScheme( final String scheme )
-    {
-        if ( scheme == null )
-        {
-            throw new NullPointerException( "scheme cannot be null" );
-        }
-        switch ( scheme )
-        {
-            case "http":
-                return 80;
-
-            case "https":
-                return 443;
-
-            case "ldap":
-                return 389;
-
-            case "ldaps":
-                return 636;
-
-            default:
-                throw new IllegalArgumentException( "unknown scheme: " + scheme );
-        }
-    }
 
     public String getPostServletPath( final PwmServletDefinition pwmServletDefinition )
     {
@@ -518,4 +527,29 @@ public class PwmURL
 
         return false;
     }
+
+    public static boolean uriSchemeMatches( final URI uri, final Scheme... schemes )
+    {
+        if ( uri == null )
+        {
+            return false;
+        }
+
+        final String schemeStr = uri.getScheme();
+
+        if ( schemeStr == null )
+        {
+            return false;
+        }
+
+        for ( final Scheme loopScheme : schemes )
+        {
+            if ( loopScheme.name().equals( schemeStr ) )
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

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

@@ -127,7 +127,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
         }
         catch ( final PwmUnrecoverableException e )
         {
-            LOGGER.error( e.getErrorInformation() );
+            LOGGER.error( pwmRequest, e.getErrorInformation() );
             pwmRequest.respondWithError( e.getErrorInformation(), true );
         }
     }
@@ -208,7 +208,7 @@ public class AuthenticationFilter extends AbstractPwmFilter
                     }
                     catch ( final Throwable e1 )
                     {
-                        LOGGER.error( () -> "error while marking pre-login url:" + e1.getMessage() );
+                        LOGGER.error( pwmRequest, () -> "error while marking pre-login url:" + e1.getMessage() );
                     }
                 }
             }

+ 37 - 27
server/src/main/java/password/pwm/http/filter/SessionFilter.java

@@ -568,14 +568,11 @@ public class SessionFilter extends AbstractPwmFilter
             throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) );
         }
 
+        // check to make sure we were not handed a non-http uri.
+        if ( !PwmURL.uriSchemeMatches( inputURI, PwmURL.Scheme.http, PwmURL.Scheme.https ) )
         {
-            // check to make sure we werent handed a non-http uri.
-            final String scheme = inputURI.getScheme();
-            if ( scheme != null && !scheme.isEmpty() && !"http".equalsIgnoreCase( scheme ) && !"https".equals( scheme ) )
-            {
-                final String errorMsg = "unsupported url scheme";
-                throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) );
-            }
+            final String errorMsg = "unsupported url scheme";
+            throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) );
         }
 
         if ( inputURI.getHost() != null && !inputURI.getHost().isEmpty() )
@@ -596,38 +593,51 @@ public class SessionFilter extends AbstractPwmFilter
             }
         }
 
+        final String testURI = copyUriWithoutParams( inputURI );
+        LOGGER.trace( sessionLabel, () -> "preparing to whitelist test parsed and decoded URL: " + testURI );
+
+        final List<String> whiteList = pwmDomain.getConfig().readSettingAsStringArray( PwmSetting.SECURITY_REDIRECT_WHITELIST );
+        if ( PwmURL.testIfUrlMatchesAllowedPattern( testURI, whiteList, sessionLabel ) )
+        {
+            return;
+        }
+
+        final String errorMsg = testURI + " is not a match for any configured redirect whitelist, see setting: "
+                + PwmSetting.SECURITY_REDIRECT_WHITELIST.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
+        throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) );
+    }
+
+    private static String copyUriWithoutParams( final URI inputURI )
+    {
         final StringBuilder sb = new StringBuilder();
-        if ( inputURI.getScheme() != null )
+
+        final String scheme = inputURI.getScheme();
+        final String host = inputURI.getHost();
+        final int port = inputURI.getPort();
+        final String path = inputURI.getPath();
+
+        if ( scheme != null )
         {
-            sb.append( inputURI.getScheme() );
+            sb.append( scheme );
             sb.append( "://" );
         }
-        if ( inputURI.getHost() != null )
+
+        if ( host != null )
         {
-            sb.append( inputURI.getHost() );
+            sb.append( host );
         }
-        if ( inputURI.getPort() != -1 )
+
+        if ( port != -1 )
         {
             sb.append( ":" );
-            sb.append( inputURI.getPort() );
-        }
-        if ( inputURI.getPath() != null )
-        {
-            sb.append( inputURI.getPath() );
+            sb.append( port );
         }
 
-        final String testURI = sb.toString();
-        LOGGER.trace( sessionLabel, () -> "preparing to whitelist test parsed and decoded URL: " + testURI );
-
-        final List<String> whiteList = pwmDomain.getConfig().readSettingAsStringArray( PwmSetting.SECURITY_REDIRECT_WHITELIST );
-
-        if ( PwmURL.testIfUrlMatchesAllowedPattern( testURI, whiteList, sessionLabel ) )
+        if ( path != null )
         {
-            return;
+            sb.append( path );
         }
 
-        final String errorMsg = testURI + " is not a match for any configured redirect whitelist, see setting: "
-                + PwmSetting.SECURITY_REDIRECT_WHITELIST.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE );
-        throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_REDIRECT_ILLEGAL, errorMsg ) );
+        return sb.toString();
     }
 }

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

@@ -240,7 +240,7 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
                 }
                 catch ( final Throwable e1 )
                 {
-                    LOGGER.error( () -> "error while marking pre-login url:" + e1.getMessage() );
+                    LOGGER.error( pwmRequest, () -> "error while marking pre-login url:" + e1.getMessage() );
                 }
                 break;
 

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

@@ -130,7 +130,7 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         }
 
         final String msg = "missing action handler for '" + action + "'";
-        LOGGER.error( () -> msg );
+        LOGGER.error( pwmRequest, () -> msg );
         throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, msg ) );
     }
 

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/accountinfo/AccountInformationBean.java

@@ -32,7 +32,7 @@ import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.DisplayElement;
-import password.pwm.http.tag.PasswordRequirementsTag;
+import password.pwm.util.password.PasswordRequirementViewableRuleGenerator;
 import password.pwm.ldap.ViewableUserInfoDisplayReader;
 import password.pwm.svc.event.UserAuditRecord;
 import password.pwm.user.UserInfo;
@@ -106,8 +106,8 @@ public class AccountInformationBean
     {
         final PwmPasswordPolicy pwmPasswordPolicy = pwmRequest.getPwmSession().getUserInfo().getPasswordPolicy();
         final MacroRequest macroRequest = pwmRequest.getMacroMachine();
-        final List<String> rules = PasswordRequirementsTag.getPasswordRequirementsStrings( pwmPasswordPolicy, pwmRequest.getDomainConfig(), pwmRequest.getLocale(), macroRequest );
-        return Collections.unmodifiableList( rules );
+        return PasswordRequirementViewableRuleGenerator
+                .generate( pwmPasswordPolicy, pwmRequest.getDomainConfig(), pwmRequest.getLocale(), macroRequest );
     }
 
     public static List<ActivityRecord> makeAuditInfo(

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

@@ -210,7 +210,7 @@ public class AppDashboardData
         builder.sessionCount( pwmDomain.getSessionTrackService().sessionCount() );
         builder.requestsInProgress( pwmDomain.getPwmApplication().getTotalActiveServletRequests() );
 
-        LOGGER.trace( () -> "AppDashboardData bean created", TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( pwmDomain.getSessionLabel(), () -> "AppDashboardData bean created", TimeDuration.fromCurrent( startTime ) );
         return builder.build();
     }
 

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

@@ -190,7 +190,7 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
         }
         catch ( final Exception e )
         {
-            LOGGER.error( () -> "error clearing wordlist " + wordlistType + ", error: " + e.getMessage() );
+            LOGGER.error( pwmRequest, () -> "error clearing wordlist " + wordlistType + ", error: " + e.getMessage() );
         }
 
         pwmRequest.outputJsonResult( RestResultBean.forSuccessMessage( pwmRequest, Message.Success_Unknown ) );

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

@@ -228,7 +228,7 @@ public abstract class ChangePasswordServlet extends ControlledPwmServlet
         }
 
         //make sure the two passwords match
-        final boolean caseSensitive = userInfo.getPasswordPolicy().getRuleHelper().readBooleanValue(
+        final boolean caseSensitive = userInfo.getPasswordPolicy().ruleHelper().readBooleanValue(
                 PwmPasswordRule.CaseSensitive );
         if ( PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH != PasswordUtility.figureMatchStatus( caseSensitive,
                 password1, password2 ) )

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

@@ -183,7 +183,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
             }
             catch ( final Exception e )
             {
-                LOGGER.error( () -> "error while testing entry DN for profile '" + profileID + "', error:" + profileID );
+                LOGGER.error( sessionLabel, () -> "error while testing entry DN for profile '" + profileID + "', error:" + profileID );
             }
             try
             {

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

@@ -52,7 +52,7 @@ import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmRequestContext;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.http.bean.ForgottenPasswordStage;
-import password.pwm.http.tag.PasswordRequirementsTag;
+import password.pwm.util.password.PasswordRequirementViewableRuleGenerator;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.UserInfoFactory;
@@ -242,7 +242,7 @@ public class ForgottenPasswordStateMachine
             final PasswordData password2 = PasswordData.forStringValue( formValues.get( PARAM_PASSWORD_CONFIRM ) );
 
             final UserInfo userInfo = UserInfoFactory.newUserInfoUsingProxy( pwmRequestContext, forgottenPasswordStateMachine.getForgottenPasswordBean().getUserIdentity() );
-            final boolean caseSensitive = userInfo.getPasswordPolicy().getRuleHelper().readBooleanValue(
+            final boolean caseSensitive = userInfo.getPasswordPolicy().ruleHelper().readBooleanValue(
                     PwmPasswordRule.CaseSensitive );
             if ( PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH != PasswordUtility.figureMatchStatus( caseSensitive,
                     password1, password2 ) )
@@ -325,7 +325,7 @@ public class ForgottenPasswordStateMachine
                     .required( true )
                     .build() );
 
-            final List<String> passwordRequirementsList = PasswordRequirementsTag.getPasswordRequirementsStrings(
+            final List<String> passwordRequirementsList = PasswordRequirementViewableRuleGenerator.generate(
                     pwmPasswordPolicy,
                     pwmRequestContext.getDomainConfig(),
                     pwmRequestContext.getLocale(),

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

@@ -41,7 +41,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.DisplayElement;
 import password.pwm.http.servlet.accountinfo.AccountInformationBean;
-import password.pwm.http.tag.PasswordRequirementsTag;
+import password.pwm.util.password.PasswordRequirementViewableRuleGenerator;
 import password.pwm.i18n.Display;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.ViewableUserInfoDisplayReader;
@@ -154,7 +154,7 @@ public class HelpdeskDetailInfoBean
         builder.passwordPolicyRules( makePasswordPolicyRules( userInfo, pwmRequest.getLocale(), pwmRequest.getDomainConfig() ) );
 
         {
-            final List<String> requirementLines = PasswordRequirementsTag.getPasswordRequirementsStrings(
+            final List<String> requirementLines = PasswordRequirementViewableRuleGenerator.generate(
                     userInfo.getPasswordPolicy(),
                     pwmRequest.getDomainConfig(),
                     pwmRequest.getLocale(),

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

@@ -248,7 +248,7 @@ public class NewUserServlet extends ControlledPwmServlet
         {
             final Instant startTime = Instant.now();
             newUserProfile.getNewUserPasswordPolicy( pwmRequest.getPwmRequestContext() );
-            LOGGER.trace( () -> "read new user password policy in ", TimeDuration.fromCurrent( startTime ) );
+            LOGGER.trace( pwmRequest, () -> "read new user password policy in ", TimeDuration.fromCurrent( startTime ) );
         }
 
         if ( !newUserBean.isFormPassed() )
@@ -478,7 +478,7 @@ public class NewUserServlet extends ControlledPwmServlet
             passwordCheckInfo = new PasswordUtility.PasswordCheckInfo( null, true, 0, PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH, 0 );
         }
 
-        LOGGER.trace( () -> "competed form validation in ", TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( pwmRequest, () -> "competed form validation in ", TimeDuration.fromCurrent( startTime ) );
         return passwordCheckInfo;
     }
 

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

@@ -738,7 +738,7 @@ class PeopleSearchDataReader
         }
         catch ( final ChaiException e )
         {
-            LOGGER.error( () -> "unexpected error during detail lookup of '" + userIdentity + "', error: " + e.getMessage() );
+            LOGGER.error( pwmRequest, () -> "unexpected error during detail lookup of '" + userIdentity + "', error: " + e.getMessage() );
             throw PwmUnrecoverableException.fromChaiException( e );
         }
     }

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

@@ -47,7 +47,6 @@ import password.pwm.http.bean.SetupResponsesBean;
 import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.i18n.Message;
-import password.pwm.user.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
@@ -55,6 +54,7 @@ import password.pwm.svc.event.AuditServiceClient;
 import password.pwm.svc.event.UserAuditRecord;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.user.UserInfo;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.logging.PwmLogger;
@@ -298,7 +298,11 @@ public class SetupResponsesServlet extends ControlledPwmServlet
             final Map<Challenge, String> responseMap = readResponsesFromJsonRequest( pwmRequest, setupData );
             final int minRandomRequiredSetup = setupData.getMinRandomSetup();
             pwmRequest.getPwmDomain().getCrService().validateResponses( challengeSet, responseMap, minRandomRequiredSetup );
+
+            final Instant startMakeResponseSet = Instant.now();
             SetupResponsesUtil.generateResponseInfoBean( pwmRequest, challengeSet, responseMap, Collections.emptyMap() );
+            LOGGER.error( pwmRequest, () -> "generated hashed response set in "
+                    + TimeDuration.fromCurrent( startMakeResponseSet ).asCompactString() );
             success = true;
         }
         catch ( final PwmDataValidationException e )

+ 11 - 27
server/src/main/java/password/pwm/http/tag/CurrentUrlTag.java

@@ -20,40 +20,24 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.http.JspUtility;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
-
-public class CurrentUrlTag extends TagSupport
+public class CurrentUrlTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( CurrentUrlTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final String currentUrl = pwmRequest.getUrlWithoutQueryString();
-            pageContext.getOut().write( StringUtil.escapeHtml( currentUrl ) );
-        }
-        catch ( final Exception e )
-        {
-            try
-            {
-                pageContext.getOut().write( "errorGeneratingCurrentURL" );
-            }
-            catch ( final IOException e1 )
-            {
-                /* ignore */
-            }
-            LOGGER.error( () -> "error during CurrentUrl output: " + e.getMessage(), e );
-        }
-        return EVAL_PAGE;
+        return pwmRequest.getUrlWithoutQueryString();
     }
 }

+ 24 - 101
server/src/main/java/password/pwm/http/tag/DisplayTag.java

@@ -20,23 +20,13 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.PwmConstants;
-import password.pwm.config.DomainConfig;
-import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.i18n.Display;
-import password.pwm.i18n.PwmDisplayBundle;
+import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-import java.util.Locale;
-import java.util.MissingResourceException;
-
 /**
  * @author Jason D. Rivard
  */
@@ -112,101 +102,34 @@ public class DisplayTag extends PwmAbstractTag
     }
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        PwmRequest pwmRequest = null;
-        try
-        {
-            try
-            {
-                pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-            }
-            catch ( final PwmException e )
-            {
-                /* noop */
-            }
-
-            final Locale locale = pwmRequest == null ? PwmConstants.DEFAULT_LOCALE : pwmRequest.getLocale();
-
-            final Class bundle = readBundle();
-            String displayMessage = figureDisplayMessage( locale, pwmRequest == null ? null : pwmRequest.getDomainConfig(), bundle );
-
-            if ( pwmRequest != null )
-            {
-                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
-                displayMessage = macroRequest.expandMacros( displayMessage );
-            }
-
-            pageContext.getOut().write( displayMessage );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.error( pwmRequest, () -> "error while executing jsp display tag: " + e.getMessage() );
-            return EVAL_PAGE;
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.error( pwmRequest, () -> "error while executing jsp display tag: " + e.getMessage(), e );
-            throw new JspTagException( e.getMessage(), e );
-        }
-        return EVAL_PAGE;
+        return LOGGER;
     }
 
-    private Class<?> readBundle( )
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        if ( bundle == null || bundle.length() < 1 )
-        {
-            return Display.class;
-        }
-
-        try
-        {
-            return Class.forName( bundle );
-        }
-        catch ( final ClassNotFoundException e )
-        {
-            /* no op */
-        }
-
-        try
-        {
-            return Class.forName( Display.class.getPackage().getName() + "." + bundle );
-        }
-        catch ( final ClassNotFoundException e )
-        {
-            /* no op */
-        }
-
-        return Display.class;
-    }
 
-    private String figureDisplayMessage( final Locale locale, final DomainConfig config, final Class<? extends PwmDisplayBundle> bundleClass )
-    {
-        try
-        {
-            return LocaleHelper.getLocalizedMessage(
-                    locale == null ? PwmConstants.DEFAULT_LOCALE : locale,
-                    key,
-                    config,
-                    bundleClass,
-                    new String[]
-                            {
-                                    value1,
-                                    value2,
-                                    value3,
-                            }
-            );
-        }
-        catch ( final MissingResourceException e )
-        {
-            if ( !displayIfMissing )
-            {
-                LOGGER.info( () -> "error while executing jsp display tag: " + e.getMessage() );
-            }
-        }
-
-        return displayIfMissing ? key : "";
+        final PwmLocaleBundle pwmBundle = PwmLocaleBundle.forKey( bundle ).orElse( PwmLocaleBundle.DISPLAY );
+
+        final String[] valueArray = new String[]
+                {
+                        value1,
+                        value2,
+                        value3,
+                };
+
+        final String rawMessage = LocaleHelper.getLocalizedMessage(
+                pwmRequest.getLocale(),
+                key,
+                pwmRequest.getDomainConfig(),
+                pwmBundle.getTheClass(),
+                valueArray );
+
+        final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
+        return macroRequest.expandMacros( rawMessage  );
     }
 }
 

+ 35 - 57
server/src/main/java/password/pwm/http/tag/ErrorMessageTag.java

@@ -21,88 +21,66 @@
 package password.pwm.http.tag;
 
 import password.pwm.AppProperty;
-import password.pwm.PwmApplication;
 import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.ContextManager;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-
 /**
  * @author Jason D. Rivard
  */
 public class ErrorMessageTag extends PwmAbstractTag
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( ErrorMessageTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-            PwmApplication pwmApplication = null;
-            try
-            {
-                pwmApplication = ContextManager.getPwmApplication( pageContext.getRequest() );
-            }
-            catch ( final PwmException e )
-            {
-                /* noop */
-            }
-
-            if ( pwmRequest == null || pwmApplication == null )
-            {
-                return EVAL_PAGE;
-            }
-
-            final ErrorInformation error = ( ErrorInformation ) pwmRequest.getAttribute( PwmRequestAttribute.PwmErrorInfo );
-
-            if ( error != null )
-            {
-                final boolean allowHtml = Boolean.parseBoolean( pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_ERRORS_ALLOW_HTML ) );
-                final boolean showErrorDetail = pwmRequest.getPwmDomain().determineIfDetailErrorMsgShown();
+        return LOGGER;
+    }
 
-                String outputMsg = error.toUserStr( pwmRequest.getLocale(), pwmRequest.getDomainConfig() );
-                if ( !allowHtml )
-                {
-                    outputMsg = StringUtil.escapeHtml( outputMsg );
-                }
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final ErrorInformation error = ( ErrorInformation ) pwmRequest.getAttribute( PwmRequestAttribute.PwmErrorInfo );
 
-                if ( showErrorDetail )
-                {
-                    final String errorDetail = error.toDebugStr() == null ? "" : " { " + error.toDebugStr() + " }";
-                    // detail should always be escaped - it may contain untrusted data
-                    outputMsg += "<span class='errorDetail'>" + StringUtil.escapeHtml( errorDetail ) + "</span>";
-                }
+        if ( error == null )
+        {
+            return "";
+        }
 
-                outputMsg = outputMsg.replace( "\n", "<br/>" );
+        final boolean allowHtml = Boolean.parseBoolean(
+                pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_ERRORS_ALLOW_HTML ) );
 
-                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
-                outputMsg = macroRequest.expandMacros( outputMsg );
+        final boolean showErrorDetail = pwmRequest.getPwmDomain().determineIfDetailErrorMsgShown();
 
-                pageContext.getOut().write( outputMsg );
-            }
-        }
-        catch ( final PwmUnrecoverableException e )
+        String outputMsg = error.toUserStr( pwmRequest.getLocale(), pwmRequest.getDomainConfig() );
+        if ( !allowHtml )
         {
-            /* app not running */
+            outputMsg = StringUtil.escapeHtml( outputMsg );
         }
-        catch ( final Exception e )
+
+        if ( showErrorDetail )
         {
-            LOGGER.error( () -> "error executing error message tag: " + e.getMessage(), e );
-            throw new JspTagException( e.getMessage() );
+            final String errorDetail = error.toDebugStr() == null
+                    ? ""
+                    : " { " + error.toDebugStr() + " }";
+
+            // detail should always be escaped - it may contain untrusted data
+            outputMsg += "<span class='errorDetail'>"
+                    + StringUtil.escapeHtml( errorDetail )
+                    + "</span>";
         }
-        return EVAL_PAGE;
+
+        outputMsg = outputMsg.replace( "\n", "<br/>" );
+
+        final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
+        outputMsg = macroRequest.expandMacros( outputMsg );
+
+        return outputMsg;
     }
 }

+ 16 - 27
server/src/main/java/password/pwm/http/tag/PasswordChangeMessageTag.java

@@ -21,51 +21,40 @@
 package password.pwm.http.tag;
 
 import password.pwm.config.profile.PwmPasswordPolicy;
-import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
 import java.util.Optional;
 
 /**
  * @author Jason D. Rivard
  */
-public class PasswordChangeMessageTag extends TagSupport
+public class PasswordChangeMessageTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PasswordChangeMessageTag.class );
 
-
     @Override
-    public int doEndTag( )
-            throws JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-
-            final PwmPasswordPolicy pwmPasswordPolicy = PasswordRequirementsTag.readPasswordPolicy( pwmRequest );
+        return LOGGER;
+    }
 
-            final Optional<String> passwordPolicyChangeMessage = pwmPasswordPolicy.getChangeMessage( pwmRequest.getLocale() );
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final PwmPasswordPolicy pwmPasswordPolicy = PasswordRequirementsTag.readPasswordPolicy( pwmRequest );
+        final Optional<String> passwordPolicyChangeMessage = pwmPasswordPolicy.getChangeMessage( pwmRequest.getLocale() );
 
-            if ( passwordPolicyChangeMessage.isPresent() )
-            {
-                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
-                final String expandedMessage = macroRequest.expandMacros( passwordPolicyChangeMessage.get() );
-                pageContext.getOut().write( expandedMessage );
-            }
-        }
-        catch ( final IOException | PwmException e )
+        if ( passwordPolicyChangeMessage.isPresent() )
         {
-            LOGGER.error( () -> "unexpected error during password change message generation: " + e.getMessage(), e );
-            throw new JspTagException( e.getMessage() );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
+            return macroRequest.expandMacros( passwordPolicyChangeMessage.get() );
         }
-        return EVAL_PAGE;
+
+        return "";
     }
 }
 

+ 35 - 526
server/src/main/java/password/pwm/http/tag/PasswordRequirementsTag.java

@@ -20,522 +20,34 @@
 
 package password.pwm.http.tag;
 
-import lombok.Value;
 import password.pwm.PwmDomain;
 import password.pwm.config.DomainConfig;
-import password.pwm.config.option.ADPolicyComplexity;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
-import password.pwm.config.profile.PwmPasswordRule;
 import password.pwm.error.PwmError;
-import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.servlet.PwmServletDefinition;
 import password.pwm.http.servlet.newuser.NewUserServlet;
-import password.pwm.i18n.Display;
-import password.pwm.i18n.Message;
-import password.pwm.util.i18n.LocaleHelper;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
-import password.pwm.util.password.PasswordRuleReaderHelper;
+import password.pwm.util.password.PasswordRequirementViewableRuleGenerator;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
-import java.util.MissingResourceException;
 import java.util.Optional;
 
 /**
  * @author Jason D. Rivard
  */
-public class PasswordRequirementsTag extends TagSupport
+public class PasswordRequirementsTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PasswordRequirementsTag.class );
     private String separator;
     private String prepend;
     private String form;
 
-    public static List<String> getPasswordRequirementsStrings(
-            final PwmPasswordPolicy passwordPolicy,
-            final DomainConfig config,
-            final Locale locale,
-            final MacroRequest macroRequest
-    )
-    {
-        final List<String> ruleTexts = new ArrayList<>(  );
-        final PolicyValues policyValues = new PolicyValues( passwordPolicy, passwordPolicy.getRuleHelper(), locale, config, macroRequest );
-        for ( final RuleTextGenerator ruleTextGenerator : RULE_TEXT_GENERATORS )
-        {
-            ruleTexts.addAll( ruleTextGenerator.generate( policyValues ) );
-        }
-
-        return Collections.unmodifiableList( ruleTexts );
-    }
-
-    private static final List<RuleTextGenerator> RULE_TEXT_GENERATORS = List.of(
-            new CaseSensitiveRuleTextGenerator(),
-            new MinLengthRuleTextGenerator(),
-            new MaxLengthRuleTextGenerator(),
-            new MinAlphaRuleTextGenerator(),
-            new MaxAlphaRuleTextGenerator(),
-            new NumericCharsRuleTextGenerator(),
-            new SpecialCharsRuleTextGenerator(),
-            new MaximumRepeatRuleTextGenerator(),
-            new MaximumSequentialRepeatRuleTextGenerator(),
-            new MinimumLowerRuleTextGenerator(),
-            new MaximumLowerRuleTextGenerator(),
-            new MinimumUpperRuleTextGenerator(),
-            new MaximumUpperRuleTextGenerator(),
-            new MinimumUniqueRuleTextGenerator(),
-            new DisallowedValuesRuleTextGenerator(),
-            new WordlistRuleTextGenerator(),
-            new DisallowedAttributesRuleTextGenerator(),
-            new MaximumOldCharsRuleTextGenerator(),
-            new MinimumLifetimeRuleTextGenerator(),
-            new ADRuleTextGenerator(),
-            new UniqueRequiredRuleTextGenerator() );
-
-    private interface RuleTextGenerator
-    {
-        List<String> generate( PolicyValues policyValues );
-    }
-
-    @Value
-    private static class PolicyValues
-    {
-        private PwmPasswordPolicy passwordPolicy;
-        private PasswordRuleReaderHelper ruleHelper;
-        private Locale locale;
-        private DomainConfig config;
-        private MacroRequest macroRequest;
-    }
-
-    private static class CaseSensitiveRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.CaseSensitive ) )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_CaseSensitive, null, policyValues ) );
-            }
-            else
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_NotCaseSensitive, null, policyValues ) );
-            }
-        }
-    }
-
-    private static class MinLengthRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLength );
-            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
-
-            if ( adPolicyLevel == ADPolicyComplexity.AD2003 || adPolicyLevel == ADPolicyComplexity.AD2008 )
-            {
-                if ( value < 6 )
-                {
-                    value = 6;
-                }
-            }
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MinLength, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaxLengthRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLength );
-            if ( value > 0 && value < 64 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxLength, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MinAlphaRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumAlpha );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MinAlpha, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaxAlphaRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumAlpha );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxAlpha, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class NumericCharsRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final PasswordRuleReaderHelper ruleHelper = policyValues.getRuleHelper();
-            if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowNumeric ) )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_AllowNumeric, null, policyValues ) );
-            }
-            else
-            {
-                final List<String> returnValues = new ArrayList<>(  );
-                final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumNumeric );
-                if ( minValue > 0 )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_MinNumeric, minValue, policyValues ) );
-                }
-
-                final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumNumeric );
-                if ( maxValue > 0 )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_MaxNumeric, maxValue, policyValues ) );
-                }
-
-                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharNumeric ) )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_FirstNumeric, maxValue, policyValues ) );
-                }
-
-                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharNumeric ) )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_LastNumeric, maxValue, policyValues ) );
-                }
-                return returnValues;
-            }
-        }
-    }
-
-    private static class SpecialCharsRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final PasswordRuleReaderHelper ruleHelper = policyValues.getRuleHelper();
-            if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowSpecial ) )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_AllowSpecial, null, policyValues ) );
-            }
-            else
-            {
-                final List<String> returnValues = new ArrayList<>(  );
-                final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumSpecial );
-                if ( minValue > 0 )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_MinSpecial, minValue, policyValues ) );
-                }
-
-                final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumSpecial );
-                if ( maxValue > 0 )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_MaxSpecial, maxValue, policyValues ) );
-                }
-
-                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharSpecial ) )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_FirstSpecial, maxValue, policyValues ) );
-                }
-
-                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharSpecial ) )
-                {
-                    returnValues.add( getLocalString( Message.Requirement_LastSpecial, maxValue, policyValues ) );
-                }
-                return Collections.unmodifiableList( returnValues );
-            }
-        }
-    }
-
-    private static class MaximumRepeatRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumRepeat );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxRepeat, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaximumSequentialRepeatRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumSequentialRepeat );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxSeqRepeat, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MinimumLowerRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLowerCase );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MinLower, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaximumLowerRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLowerCase );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxLower, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MinimumUpperRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumUpperCase );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MinUpper, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaximumUpperRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumUpperCase );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MaxUpper, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MinimumUniqueRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumUnique );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_MinUnique, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class DisallowedValuesRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final List<String> setValue = policyValues.getRuleHelper().getDisallowedValues();
-            if ( !setValue.isEmpty() )
-            {
-                final StringBuilder fieldValue = new StringBuilder();
-                for ( final String loopValue : setValue )
-                {
-                    fieldValue.append( ' ' );
-
-                    final String expandedValue = policyValues.getMacroRequest().expandMacros( loopValue );
-                    fieldValue.append( StringUtil.escapeHtml( expandedValue ) );
-                }
-                return Collections.singletonList( getLocalString( Message.Requirement_DisAllowedValues, fieldValue.toString(), policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class WordlistRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.EnableWordlist ) )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_WordList, "", policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class DisallowedAttributesRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final List<String> setValue = policyValues.getRuleHelper().getDisallowedAttributes();
-            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
-            if ( !setValue.isEmpty() || adPolicyLevel == ADPolicyComplexity.AD2003 )
-            {
-                return Collections.singletonList(  getLocalString( Message.Requirement_DisAllowedAttributes, "", policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MaximumOldCharsRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MaximumOldChars );
-            if ( value > 0 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_OldChar, value, policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class MinimumLifetimeRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final int value = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
-
-            if ( value <= 0 )
-            {
-                return Collections.emptyList();
-            }
-
-            final int secondsPerDay = 60 * 60 * 24;
-
-            final String durationStr;
-            if ( value % secondsPerDay == 0 )
-            {
-                final int valueAsDays = value / ( 60 * 60 * 24 );
-                final Display key = valueAsDays <= 1 ? Display.Display_Day : Display.Display_Days;
-                durationStr = valueAsDays + " " + LocaleHelper.getLocalizedMessage( policyValues.getLocale(), key, policyValues.getConfig() );
-            }
-            else
-            {
-                final int valueAsHours = value / ( 60 * 60 );
-                final Display key = valueAsHours <= 1 ? Display.Display_Hour : Display.Display_Hours;
-                durationStr = valueAsHours + " " + LocaleHelper.getLocalizedMessage( policyValues.getLocale(), key, policyValues.getConfig() );
-            }
-
-            final String userMsg = Message.getLocalizedMessage( policyValues.getLocale(), Message.Requirement_MinimumFrequency, policyValues.getConfig(), durationStr );
-            return Collections.singletonList( userMsg );
-        }
-    }
-
-    private static class ADRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            final ADPolicyComplexity adPolicyLevel = policyValues.getRuleHelper().getADComplexityLevel();
-            if ( adPolicyLevel == ADPolicyComplexity.AD2003 )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity, "", policyValues ) );
-            }
-            else if ( adPolicyLevel == ADPolicyComplexity.AD2008 )
-            {
-                final int maxViolations = policyValues.getRuleHelper().readIntValue( PwmPasswordRule.ADComplexityMaxViolations );
-                final int minGroups = 5 - maxViolations;
-                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity2008, String.valueOf( minGroups ), policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static class UniqueRequiredRuleTextGenerator implements RuleTextGenerator
-    {
-        @Override
-        public List<String> generate( final PolicyValues policyValues )
-        {
-            if ( policyValues.getRuleHelper().readBooleanValue( PwmPasswordRule.UniqueRequired ) )
-            {
-                return Collections.singletonList( getLocalString( Message.Requirement_UniqueRequired, "", policyValues ) );
-            }
-            return Collections.emptyList();
-        }
-    }
-
-    private static String getLocalString( final Message message, final int size, final PolicyValues policyValues )
-    {
-        final Message effectiveMessage = size > 1 && message.getPluralMessage() != null
-                ? message.getPluralMessage()
-                : message;
-
-        try
-        {
-            return Message.getLocalizedMessage( policyValues.getLocale(), effectiveMessage, policyValues.getConfig(), String.valueOf( size ) );
-        }
-        catch ( final MissingResourceException e )
-        {
-            LOGGER.error( () -> "unable to display requirement tag for message '" + message.toString() + "': " + e.getMessage() );
-        }
-        return "UNKNOWN MESSAGE STRING";
-    }
-
-    private static String getLocalString( final Message message, final String field, final PolicyValues policyValues )
-    {
-        try
-        {
-            return Message.getLocalizedMessage( policyValues.getLocale(), message, policyValues.getConfig(), field );
-        }
-        catch ( final MissingResourceException e )
-        {
-            LOGGER.error( () -> "unable to display requirement tag for message '" + message.toString() + "': " + e.getMessage() );
-        }
-        return "UNKNOWN MESSAGE STRING";
-    }
-
     public String getSeparator( )
     {
         return separator;
@@ -567,51 +79,48 @@ public class PasswordRequirementsTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-            final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-            final DomainConfig config = pwmDomain.getConfig();
-            final Locale locale = pwmSession.getSessionStateBean().getLocale();
+        return LOGGER;
+    }
 
-            pwmRequest.getMacroMachine( );
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final PwmSession pwmSession = pwmRequest.getPwmSession();
+        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
+        final DomainConfig config = pwmDomain.getConfig();
+        final Locale locale = pwmSession.getSessionStateBean().getLocale();
 
-            final PwmPasswordPolicy passwordPolicy = readPasswordPolicy( pwmRequest );
+        pwmRequest.getMacroMachine( );
 
-            final Optional<String> configuredRuleText = passwordPolicy.getRuleText( pwmRequest.getLocale() );
-            if ( configuredRuleText.isPresent() )
-            {
-                pageContext.getOut().write( configuredRuleText.get() );
-            }
-            else
-            {
-                final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
+        final PwmPasswordPolicy passwordPolicy = readPasswordPolicy( pwmRequest );
 
-                final String pre = prepend != null && prepend.length() > 0 ? prepend : "";
-                final String sep = separator != null && separator.length() > 0 ? separator : "<br/>";
-                final List<String> requirementsList = getPasswordRequirementsStrings( passwordPolicy, config, locale, macroRequest );
+        final Optional<String> configuredRuleText = passwordPolicy.getRuleText( pwmRequest.getLocale() );
+        if ( configuredRuleText.isPresent() )
+        {
+            return configuredRuleText.get();
+        }
+        else
+        {
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
 
-                final StringBuilder requirementsText = new StringBuilder();
-                for ( final String requirementStatement : requirementsList )
-                {
-                    requirementsText.append( pre );
-                    requirementsText.append( requirementStatement );
-                    requirementsText.append( sep );
-                }
+            final String pre = prepend != null && prepend.length() > 0 ? prepend : "";
+            final String sep = separator != null && separator.length() > 0 ? separator : "<br/>";
+            final List<String> requirementsList = PasswordRequirementViewableRuleGenerator
+                    .generate( passwordPolicy, config, locale, macroRequest );
 
-                pageContext.getOut().write( requirementsText.toString() );
+            final StringBuilder requirementsText = new StringBuilder();
+            for ( final String requirementStatement : requirementsList )
+            {
+                requirementsText.append( pre );
+                requirementsText.append( requirementStatement );
+                requirementsText.append( sep );
             }
+
+            return requirementsText.toString();
         }
-        catch ( final IOException | PwmException e )
-        {
-            LOGGER.error( () -> "unexpected error during password requirements generation: " + e.getMessage(), e );
-            throw new JspTagException( e.getMessage() );
-        }
-        return EVAL_PAGE;
     }
 
     static PwmPasswordPolicy readPasswordPolicy( final PwmRequest pwmRequest )

+ 44 - 0
server/src/main/java/password/pwm/http/tag/PwmAbstractTag.java

@@ -20,6 +20,14 @@
 
 package password.pwm.http.tag;
 
+import password.pwm.PwmApplicationMode;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.util.logging.PwmLogger;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspTagException;
 import javax.servlet.jsp.tagext.TagSupport;
 
 /**
@@ -27,5 +35,41 @@ import javax.servlet.jsp.tagext.TagSupport;
  */
 public abstract class PwmAbstractTag extends TagSupport
 {
+    @Override
+    public int doEndTag( )
+            throws JspTagException
+    {
+        if ( PwmApplicationMode.determineMode( ( HttpServletRequest ) pageContext.getRequest() ) == PwmApplicationMode.ERROR )
+        {
+            return EVAL_PAGE;
+        }
+
+        try
+        {
+            final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest();
+            final PwmRequest pwmRequest = PwmRequest.forRequest( req, ( HttpServletResponse ) pageContext.getResponse() );
+            try
+            {
+                final String contents = generateTagBodyContents( pwmRequest );
+                pageContext.getOut().write( contents );
+            }
+            catch ( final Throwable e )
+            {
+                getLogger().error( pwmRequest, () -> "error processing JSP tag "
+                        + this.getClass().getName()
+                        + ", error: " + e.getMessage(), e );
+            }
+        }
+        catch ( final Throwable e )
+        {
+            throw new JspTagException( e.getMessage(), e );
+        }
+        return EVAL_PAGE;
+    }
+
+    protected abstract PwmLogger getLogger();
+
+    protected abstract String generateTagBodyContents( PwmRequest pwmRequest )
+            throws PwmUnrecoverableException;
 }
 

+ 19 - 18
server/src/main/java/password/pwm/http/tag/PwmAutofocusTag.java

@@ -20,30 +20,31 @@
 
 package password.pwm.http.tag;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.PwmRequest;
+import password.pwm.http.PwmRequestAttribute;
+import password.pwm.util.logging.PwmLogger;
 
-public class PwmAutofocusTag extends TagSupport
+public class PwmAutofocusTag extends PwmAbstractTag
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmAutofocusTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest();
-            if ( req.getAttribute( "autoFocusHasBeenSet" ) == null )
-            {
-                pageContext.getOut().write( "autofocus=\"autofocus\" " );
-                req.setAttribute( "autoFocusHasBeenSet", true );
-            }
-        }
-        catch ( final Exception e )
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.getAttribute( PwmRequestAttribute.JspAutofocusStatus ) == null )
         {
-            throw new JspTagException( e.getMessage() );
+            pwmRequest.setAttribute( PwmRequestAttribute.JspAutofocusStatus, true );
+            return "autofocus=\"autofocus\" ";
         }
-        return EVAL_PAGE;
+
+        return "";
     }
 }

+ 12 - 21
server/src/main/java/password/pwm/http/tag/PwmContextTag.java

@@ -20,33 +20,24 @@
 
 package password.pwm.http.tag;
 
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
+import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
-
-public class PwmContextTag extends TagSupport
+public class PwmContextTag extends PwmAbstractTag
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PwmContextTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest();
-            final HttpServletResponse resp = ( HttpServletResponse ) pageContext.getResponse();
+        return LOGGER;
+    }
 
-            final PwmRequest pwmRequest = PwmRequest.forRequest( req, resp );
-            final String path = pwmRequest.getBasePath();
-            pageContext.getOut().write( path );
-        }
-        catch ( final Exception e )
-        {
-            throw new JspTagException( e.getMessage() );
-        }
-        return EVAL_PAGE;
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        return pwmRequest.getBasePath();
     }
 }

+ 14 - 51
server/src/main/java/password/pwm/http/tag/PwmFormIDTag.java

@@ -20,41 +20,35 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.PwmDomain;
-import password.pwm.PwmApplicationMode;
 import password.pwm.bean.FormNonce;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.JspUtility;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.state.SessionStateService;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
 import java.time.Instant;
 
-public class PwmFormIDTag extends TagSupport
+public class PwmFormIDTag extends PwmAbstractTag
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmFormIDTag.class );
 
-    private static String buildPwmFormID( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    @Override
+    protected PwmLogger getLogger()
     {
-        if ( pwmRequest == null || pwmRequest.getPwmDomain() == null )
-        {
-            return "";
-        }
-
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        if ( pwmDomain == null )
-        {
-            return "";
-        }
+        return LOGGER;
+    }
 
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        return buildPwmFormID( pwmRequest );
+    }
 
-        final SessionStateService sessionStateService = pwmDomain.getSessionStateService();
+    private static String buildPwmFormID( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    {
+        final SessionStateService sessionStateService = pwmRequest.getPwmDomain().getSessionStateService();
         final String value = sessionStateService.getSessionStateInfo( pwmRequest );
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final FormNonce formID = new FormNonce(
@@ -65,35 +59,4 @@ public class PwmFormIDTag extends TagSupport
         );
         return pwmRequest.getPwmDomain().getSecureService().encryptObjectToString( formID );
     }
-
-    @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
-    {
-        if ( PwmApplicationMode.determineMode( ( HttpServletRequest ) pageContext.getRequest() ) == PwmApplicationMode.ERROR )
-        {
-            return EVAL_PAGE;
-        }
-
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final String pwmFormID = buildPwmFormID( pwmRequest );
-
-            pageContext.getOut().write( pwmFormID );
-        }
-        catch ( final Exception e )
-        {
-            try
-            {
-                pageContext.getOut().write( "errorGeneratingPwmFormID" );
-            }
-            catch ( final IOException e1 )
-            {
-                /* ignore */
-            }
-            LOGGER.error( () -> "error during pwmFormIDTag output of pwmFormID: " + e.getMessage(), e );
-        }
-        return EVAL_PAGE;
-    }
 }

+ 12 - 25
server/src/main/java/password/pwm/http/tag/PwmMacroTag.java

@@ -25,18 +25,13 @@ import password.pwm.http.PwmRequest;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
-
-public class PwmMacroTag extends TagSupport
+public class PwmMacroTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmMacroTag.class );
 
     private String value;
 
-    public String getValue( )
+    public String getValue()
     {
         return value;
     }
@@ -47,24 +42,16 @@ public class PwmMacroTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws JspTagException
+    protected PwmLogger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        try
-        {
-            final PwmRequest pwmRequest = PwmRequest.forRequest( ( HttpServletRequest ) pageContext.getRequest(), ( HttpServletResponse ) pageContext.getResponse() );
-            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
-            final String outputValue = macroRequest.expandMacros( value );
-            pageContext.getOut().write( outputValue );
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.error( () -> "error while processing PwmMacroTag: " + e.getMessage() );
-        }
-        catch ( final Exception e )
-        {
-            throw new JspTagException( e.getMessage(), e );
-        }
-        return EVAL_PAGE;
+        final MacroRequest macroRequest = pwmRequest.getMacroMachine();
+        return macroRequest.expandMacros( value );
     }
 }

+ 20 - 23
server/src/main/java/password/pwm/http/tag/PwmScriptRefTag.java

@@ -20,14 +20,12 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.http.JspUtility;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.tag.url.PwmUrlTag;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.jsp.tagext.TagSupport;
-
-public class PwmScriptRefTag extends TagSupport
+public class PwmScriptRefTag extends PwmAbstractTag
 {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmScriptRefTag.class );
@@ -45,27 +43,26 @@ public class PwmScriptRefTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final String cspNonce = pwmRequest.getCspNonce();
+        final String cspNonce = pwmRequest.getCspNonce();
 
-            String url = getUrl();
-            url = PwmUrlTag.convertUrl( url );
-            url = PwmUrlTag.insertContext( pageContext, url );
-            url = PwmUrlTag.insertResourceNonce( pwmRequest.getPwmDomain(), url );
+        String url = getUrl();
+        url = PwmUrlTag.convertUrl( url );
+        url = PwmUrlTag.insertContext( pageContext, url );
+        url = PwmUrlTag.insertResourceNonce( pwmRequest.getPwmDomain(), url );
 
-            final String output = "<script type=\"text/javascript\" nonce=\"" + cspNonce + "\" src=\"" + url + "\"></script><noscript></noscript>";
-            pageContext.getOut().write( output );
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.error( () -> "error during scriptRef output of pwmFormID: " + e.getMessage() );
-        }
-        return EVAL_PAGE;
+        return "<script type=\"text/javascript\" nonce=\""
+                + cspNonce
+                + "\" src=\""
+                + url
+                + "\"></script><noscript></noscript>";
     }
-
 }

+ 14 - 28
server/src/main/java/password/pwm/http/tag/PwmTabIndexTag.java

@@ -20,42 +20,28 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.http.JspUtility;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
-
-public class PwmTabIndexTag extends TagSupport
+public class PwmTabIndexTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmTabIndexTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final Integer currentCounter = (Integer) pwmRequest.getAttribute( PwmRequestAttribute.JspIndexTabCounter );
-            final int nextCounter = currentCounter == null ? 1 : currentCounter + 1;
-            pageContext.getOut().write( String.valueOf( nextCounter ) );
-            pwmRequest.setAttribute( PwmRequestAttribute.JspIndexTabCounter, nextCounter );
-        }
-        catch ( final Exception e )
-        {
-            try
-            {
-                pageContext.getOut().write( "" );
-            }
-            catch ( final IOException e1 )
-            {
-                /* ignore */
-            }
-            LOGGER.error( () -> "error during PwmTabIndexTag output: " + e.getMessage(), e );
-        }
-        return EVAL_PAGE;
+        final Integer currentCounter = (Integer) pwmRequest.getAttribute( PwmRequestAttribute.JspIndexTabCounter );
+        final int nextCounter = currentCounter == null ? 1 : currentCounter + 1;
+        pwmRequest.setAttribute( PwmRequestAttribute.JspIndexTabCounter, nextCounter );
+        return String.valueOf( nextCounter );
     }
 }

+ 13 - 30
server/src/main/java/password/pwm/http/tag/PwmTextFileTag.java

@@ -20,17 +20,15 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.http.JspUtility;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.servlet.resource.TextFileResource;
 import password.pwm.http.tag.value.PwmValueTag;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
 import java.util.Optional;
 
-public class PwmTextFileTag extends TagSupport
+public class PwmTextFileTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmValueTag.class );
 
@@ -47,37 +45,22 @@ public class PwmTextFileTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
+    {
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
     {
         if ( textFileResource == null )
         {
-            return EVAL_PAGE;
+            return "";
         }
 
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final Optional<String> output = TextFileResource.readTextFileResource( pwmRequest, getTextFileResource() );
-
-            if ( output.isPresent() )
-            {
-                pageContext.getOut().write( output.get() );
-            }
-        }
-        catch ( final Exception e )
-        {
-            try
-            {
-                pageContext.getOut().write( "errorGeneratingPwmFormID" );
-            }
-            catch ( final IOException e1 )
-            {
-                /* ignore */
-            }
-            LOGGER.error( () -> "error during PwmTextFileTag output of PwmTextFileTag: " + e.getMessage(), e );
-        }
+        final Optional<String> output = TextFileResource.readTextFileResource( pwmRequest, getTextFileResource() );
 
-        return EVAL_PAGE;
+        return output.orElse( "" );
     }
 }

+ 21 - 33
server/src/main/java/password/pwm/http/tag/SuccessMessageTag.java

@@ -20,57 +20,45 @@
 
 package password.pwm.http.tag;
 
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.i18n.Message;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
-
 /**
  * @author Jason D. Rivard
  */
 public class SuccessMessageTag extends PwmAbstractTag
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( SuccessMessageTag.class );
 
     @Override
-    public int doEndTag( )
-            throws javax.servlet.jsp.JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest();
-            final PwmRequest pwmRequest = PwmRequest.forRequest( req, ( HttpServletResponse ) pageContext.getResponse() );
-
-            final String successMsg = ( String ) pwmRequest.getAttribute( PwmRequestAttribute.SuccessMessage );
+        return LOGGER;
+    }
 
-            final String outputMsg;
-            if ( successMsg == null || successMsg.isEmpty() )
-            {
-                outputMsg = Message.getLocalizedMessage( pwmRequest.getLocale(), Message.Success_Unknown, pwmRequest.getDomainConfig() );
-            }
-            else
-            {
-                if ( pwmRequest.isAuthenticated() )
-                {
-                    final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
-                    outputMsg = macroRequest.expandMacros( successMsg );
-                }
-                else
-                {
-                    outputMsg = successMsg;
-                }
-            }
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        final String successMsg = ( String ) pwmRequest.getAttribute( PwmRequestAttribute.SuccessMessage );
 
-            pageContext.getOut().write( outputMsg );
+        if ( StringUtil.isEmpty( successMsg ) )
+        {
+            return Message.getLocalizedMessage( pwmRequest.getLocale(), Message.Success_Unknown, pwmRequest.getDomainConfig() );
         }
-        catch ( final Exception e )
+
+        if ( pwmRequest.isAuthenticated() )
         {
-            throw new JspTagException( e.getMessage() );
+            final MacroRequest macroRequest = pwmRequest.getMacroMachine( );
+            return macroRequest.expandMacros( successMsg );
         }
-        return EVAL_PAGE;
+
+        return successMsg;
     }
 }
 

+ 21 - 21
server/src/main/java/password/pwm/http/tag/UserInfoTag.java

@@ -20,19 +20,18 @@
 
 package password.pwm.http.tag;
 
-import password.pwm.http.JspUtility;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
-import password.pwm.http.PwmSession;
 import password.pwm.util.java.StringUtil;
-
-import javax.servlet.jsp.JspTagException;
-import javax.servlet.jsp.tagext.TagSupport;
+import password.pwm.util.logging.PwmLogger;
 
 /**
  * @author Jason D. Rivard
  */
-public class UserInfoTag extends TagSupport
+public class UserInfoTag extends PwmAbstractTag
 {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserInfoTag.class );
+
     private String attribute;
 
     public void setAttribute( final String attribute )
@@ -41,24 +40,25 @@ public class UserInfoTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws JspTagException
+    protected PwmLogger getLogger()
     {
-        try
-        {
-            final PwmRequest pwmRequest = JspUtility.getPwmRequest( pageContext );
-            final PwmSession pwmSession = pwmRequest.getPwmSession();
-            if ( pwmSession.isAuthenticated() )
-            {
-                final String ldapValue = pwmSession.getUserInfo().readStringAttribute( attribute );
-                pageContext.getOut().write( StringUtil.escapeHtml( ldapValue == null ? "" : ldapValue ) );
-            }
-        }
-        catch ( final Exception e )
+        return LOGGER;
+    }
+
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException
+    {
+        if ( pwmRequest.isAuthenticated() )
         {
-            throw new JspTagException( e.getMessage() );
+            final String ldapValue = pwmRequest.getPwmSession().getUserInfo()
+                    .readStringAttribute( attribute );
+
+            return StringUtil.escapeHtml( ldapValue == null ? "" : ldapValue );
         }
-        return EVAL_PAGE;
+
+        return "";
     }
+
 }
 

+ 3 - 3
server/src/main/java/password/pwm/http/tag/url/PwmUrlTag.java

@@ -21,23 +21,23 @@
 package password.pwm.http.tag.url;
 
 import password.pwm.AppProperty;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.config.PwmSetting;
 import password.pwm.error.PwmException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
 import password.pwm.http.servlet.resource.ResourceFileServlet;
-import password.pwm.http.tag.PwmAbstractTag;
 import password.pwm.util.java.StringUtil;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.jsp.JspTagException;
 import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.TagSupport;
 
-public class PwmUrlTag extends PwmAbstractTag
+public class PwmUrlTag extends TagSupport
 {
 
     private String url;

+ 13 - 42
server/src/main/java/password/pwm/http/tag/value/PwmValueTag.java

@@ -20,22 +20,17 @@
 
 package password.pwm.http.tag.value;
 
-import password.pwm.PwmApplicationMode;
-import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
+import password.pwm.http.tag.PwmAbstractTag;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.logging.PwmLogger;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspTagException;
 import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.tagext.TagSupport;
 
 /**
  * @author Jason D. Rivard
  */
-public class PwmValueTag extends TagSupport
+public class PwmValueTag extends PwmAbstractTag
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmValueTag.class );
 
@@ -52,42 +47,19 @@ public class PwmValueTag extends TagSupport
     }
 
     @Override
-    public int doEndTag( )
-            throws JspTagException
+    protected PwmLogger getLogger()
     {
-        if ( PwmApplicationMode.determineMode( ( HttpServletRequest ) pageContext.getRequest() ) == PwmApplicationMode.ERROR )
-        {
-            return EVAL_PAGE;
-        }
-
-        try
-        {
-            final HttpServletRequest req = ( HttpServletRequest ) pageContext.getRequest();
-            final PwmRequest pwmRequest = PwmRequest.forRequest( req, ( HttpServletResponse ) pageContext.getResponse() );
-            try
-            {
-                final PwmValue value = getName();
-                final String output = calcValue( pwmRequest, pageContext, value );
-                final String escapedOutput = value.getFlags().contains( PwmValue.Flag.DoNotEscape )
-                        ? output
-                        : StringUtil.escapeHtml( output );
-                pageContext.getOut().write( escapedOutput );
+        return LOGGER;
+    }
 
-            }
-            catch ( final IllegalArgumentException e )
-            {
-                LOGGER.error( () -> "can't output requested value name '" + getName() + "'" );
-            }
-        }
-        catch ( final PwmUnrecoverableException e )
-        {
-            LOGGER.error( () -> "error while processing PwmValueTag: " + e.getMessage() );
-        }
-        catch ( final Exception e )
-        {
-            throw new JspTagException( e.getMessage(), e );
-        }
-        return EVAL_PAGE;
+    @Override
+    protected String generateTagBodyContents( final PwmRequest pwmRequest )
+    {
+        final PwmValue value = getName();
+        final String output = calcValue( pwmRequest, pageContext, value );
+        return value.getFlags().contains( PwmValue.Flag.DoNotEscape )
+                ? output
+                : StringUtil.escapeHtml( output );
     }
 
     public String calcValue(
@@ -96,7 +68,6 @@ public class PwmValueTag extends TagSupport
             final PwmValue value
     )
     {
-
         if ( value != null )
         {
             try

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

@@ -253,7 +253,7 @@ public class LdapDomainService extends AbstractPwmService implements PwmService
         {
             final String errorMsg = "unexpected error creating new proxy ldap connection: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg );
-            LOGGER.error( errorInformation );
+            LOGGER.error( sessionLabel, errorInformation );
             throw new PwmUnrecoverableException( errorInformation );
         }
     }

+ 4 - 4
server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java

@@ -228,7 +228,7 @@ public class LdapUserInfoReader implements UserInfo
         LOGGER.trace( sessionLabel, () -> "beginning password status check process for " + userDN );
 
         // check if password meets existing policy.
-        if ( passwordPolicy.getRuleHelper().readBooleanValue( PwmPasswordRule.EnforceAtLogin ) )
+        if ( passwordPolicy.ruleHelper().readBooleanValue( PwmPasswordRule.EnforceAtLogin ) )
         {
             if ( currentPassword != null )
             {
@@ -678,9 +678,9 @@ public class LdapUserInfoReader implements UserInfo
     )
             throws PwmUnrecoverableException
     {
-        final Set<String> interestingUserAttributes = new HashSet<>( uiBean.getPasswordPolicy().getRuleHelper().getDisallowedAttributes() );
-        if ( uiBean.getPasswordPolicy().getRuleHelper().getADComplexityLevel() == ADPolicyComplexity.AD2003
-                || uiBean.getPasswordPolicy().getRuleHelper().getADComplexityLevel() == ADPolicyComplexity.AD2008 )
+        final Set<String> interestingUserAttributes = new HashSet<>( uiBean.getPasswordPolicy().ruleHelper().getDisallowedAttributes() );
+        if ( uiBean.getPasswordPolicy().ruleHelper().getADComplexityLevel() == ADPolicyComplexity.AD2003
+                || uiBean.getPasswordPolicy().ruleHelper().getADComplexityLevel() == ADPolicyComplexity.AD2008 )
         {
             interestingUserAttributes.add( "sAMAccountName" );
             interestingUserAttributes.add( "displayName" );

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

@@ -82,7 +82,7 @@ public class SimpleLdapAuthenticator
                 PwmError.ERROR_INTERNAL,
                 "auth with unexpected auth type: " + authResult.getAuthenticationType()
         );
-        LOGGER.error( errorInformation );
+        LOGGER.error( sessionLabel, errorInformation );
         throw new PwmUnrecoverableException( errorInformation );
     }
 }

+ 1 - 1
server/src/main/java/password/pwm/svc/email/EmailService.java

@@ -553,7 +553,7 @@ public class EmailService extends AbstractPwmService implements PwmService
                 emailConnection.getEmailServer().getConnectionStats().increment( EmailServer.ServerStat.sendFailures );
 
             }
-            LOGGER.error( errorInformation );
+            LOGGER.error( getSessionLabel(), errorInformation );
             throw e;
         }
         finally

+ 4 - 9
server/src/main/java/password/pwm/svc/httpclient/ApachePwmHttpClient.java

@@ -87,7 +87,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
 import java.security.SecureRandom;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
@@ -404,13 +403,9 @@ public class ApachePwmHttpClient implements AutoCloseable, PwmHttpClientProvider
     public InputStream streamForUrl( final String inputUrl )
             throws IOException, PwmUnrecoverableException
     {
-        final URL url = new URL( inputUrl );
-        if ( "file".equals( url.getProtocol() ) )
-        {
-            return url.openStream();
-        }
+        final URI uri = URI.create( inputUrl );
 
-        if ( "http".equals( url.getProtocol() ) || "https".equals( url.getProtocol() ) )
+        if ( PwmURL.uriSchemeMatches( uri, PwmURL.Scheme.http, PwmURL.Scheme.https ) )
         {
 
             final PwmHttpClientRequest pwmHttpClientRequest = PwmHttpClientRequest.builder()
@@ -423,13 +418,13 @@ public class ApachePwmHttpClient implements AutoCloseable, PwmHttpClientProvider
             {
                 final String errorMsg = "error retrieving stream for url '" + inputUrl + "', remote response: " + httpResponse.getStatusLine().toString();
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_REMOTE_ERROR_VALUE, errorMsg );
-                LOGGER.error( errorInformation );
+                LOGGER.error( sessionLabel, errorInformation );
                 throw new PwmUnrecoverableException( errorInformation );
             }
             return httpResponse.getEntity().getContent();
         }
 
-        throw new IllegalArgumentException( "unknown protocol type: " + url.getProtocol() );
+        throw new IllegalArgumentException( "unknown protocol type: " + uri.getScheme() );
     }
 
     private static class ProxyRoutePlanner implements HttpRoutePlanner

+ 5 - 9
server/src/main/java/password/pwm/svc/httpclient/JavaPwmHttpClient.java

@@ -35,6 +35,7 @@ import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.data.ImmutableByteArray;
+import password.pwm.http.PwmURL;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
@@ -51,7 +52,6 @@ import java.io.InputStream;
 import java.net.InetSocketAddress;
 import java.net.ProxySelector;
 import java.net.URI;
-import java.net.URL;
 import java.net.http.HttpClient;
 import java.net.http.HttpHeaders;
 import java.net.http.HttpRequest;
@@ -306,13 +306,9 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
     public InputStream streamForUrl( final String inputUrl )
             throws IOException, PwmUnrecoverableException
     {
-        final URL url = new URL( inputUrl );
-        if ( "file".equals( url.getProtocol() ) )
-        {
-            return url.openStream();
-        }
+        final URI uri = URI.create( inputUrl );
 
-        if ( "http".equals( url.getProtocol() ) || "https".equals( url.getProtocol() ) )
+        if ( PwmURL.uriSchemeMatches( uri, PwmURL.Scheme.http, PwmURL.Scheme.https ) )
         {
             try
             {
@@ -322,7 +318,7 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
                 {
                     final String errorMsg = "error retrieving stream for url '" + inputUrl + "', remote response: " + response.statusCode();
                     final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_HTTP_CLIENT, errorMsg );
-                    LOGGER.error( errorInformation );
+                    LOGGER.error( sessionLabel, errorInformation );
                     throw new PwmUnrecoverableException( errorInformation );
                 }
                 return response.body();
@@ -334,7 +330,7 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
             }
         }
 
-        throw new IllegalArgumentException( "unknown protocol type: " + url.getProtocol() );
+        throw new IllegalArgumentException( "unknown protocol type: " + uri.getScheme() );
     }
 
     @Override

+ 7 - 9
server/src/main/java/password/pwm/svc/node/NodeMachine.java

@@ -20,8 +20,6 @@
 
 package password.pwm.svc.node;
 
-import password.pwm.PwmApplication;
-import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -40,7 +38,7 @@ class NodeMachine
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( NodeMachine.class );
 
-    private final PwmApplication pwmApplication;
+    private final NodeService nodeService;
     private final NodeDataServiceProvider clusterDataServiceProvider;
 
     private ErrorInformation lastError;
@@ -51,12 +49,12 @@ class NodeMachine
     private final NodeServiceStatistics nodeServiceStatistics = new NodeServiceStatistics();
 
     NodeMachine(
-            final PwmApplication pwmApplication,
+            final NodeService nodeService,
             final NodeDataServiceProvider clusterDataServiceProvider,
             final NodeServiceSettings nodeServiceSettings
     )
     {
-        this.pwmApplication = pwmApplication;
+        this.nodeService = nodeService;
         this.clusterDataServiceProvider = clusterDataServiceProvider;
         this.settings = nodeServiceSettings;
     }
@@ -69,7 +67,7 @@ class NodeMachine
     public List<NodeInfo> nodes( ) throws PwmUnrecoverableException
     {
         final Map<String, NodeInfo> returnObj = new TreeMap<>();
-        final String configHash = StoredConfigurationUtil.valueHash( pwmApplication.getConfig().getStoredConfiguration() );
+        final String configHash = nodeService.getPwmApp().getConfig().getValueHash();
         for ( final StoredNodeData storedNodeData : knownNodes.values() )
         {
             final boolean configMatch = configHash.equals( storedNodeData.getConfigHash() );
@@ -127,7 +125,7 @@ class NodeMachine
 
     public boolean isMaster( )
     {
-        final String myID = pwmApplication.getInstanceID();
+        final String myID = nodeService.getPwmApp().getInstanceID();
         final String masterID = masterInstanceId();
         return myID.equals( masterID );
     }
@@ -169,7 +167,7 @@ class NodeMachine
             catch ( final PwmUnrecoverableException e )
             {
                 lastError = e.getErrorInformation();
-                LOGGER.error( e.getErrorInformation() );
+                LOGGER.error( nodeService.getSessionLabel(), e.getErrorInformation() );
             }
         }
 
@@ -178,7 +176,7 @@ class NodeMachine
         {
             try
             {
-                final StoredNodeData storedNodeData = StoredNodeData.makeNew( pwmApplication );
+                final StoredNodeData storedNodeData = StoredNodeData.makeNew( nodeService.getPwmApp() );
                 clusterDataServiceProvider.writeNodeStatus( storedNodeData );
                 nodeServiceStatistics.getClusterWrites().incrementAndGet();
             }

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

@@ -94,7 +94,7 @@ public class NodeService extends AbstractPwmService implements PwmService
 
                 }
 
-                nodeMachine = new NodeMachine( pwmApplication, clusterDataServiceProvider, nodeServiceSettings );
+                nodeMachine = new NodeMachine( this, clusterDataServiceProvider, nodeServiceSettings );
                 scheduleFixedRateJob( nodeMachine.getHeartbeatProcess(), nodeServiceSettings.getHeartbeatInterval(), nodeServiceSettings.getHeartbeatInterval() );
             }
         }
@@ -107,7 +107,7 @@ public class NodeService extends AbstractPwmService implements PwmService
         catch ( final Exception e )
         {
             setStartupError( new ErrorInformation( PwmError.ERROR_NODE_SERVICE_ERROR, "error starting up node service: " + e.getMessage() ) );
-            LOGGER.error( getStartupError() );
+            LOGGER.error( getSessionLabel(), getStartupError() );
             return STATUS.CLOSED;
         }
 
@@ -207,4 +207,9 @@ public class NodeService extends AbstractPwmService implements PwmService
             }
         }
     }
+
+    public PwmApplication getPwmApp()
+    {
+        return getPwmApplication();
+    }
 }

+ 2 - 2
server/src/main/java/password/pwm/svc/otp/DbOtpOperator.java

@@ -151,13 +151,13 @@ public class DbOtpOperator extends AbstractOtpOperator
                     ) );
         }
 
-        LOGGER.trace( () -> "attempting to clear OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" );
+        LOGGER.trace( pwmRequest, () -> "attempting to clear OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" );
 
         try
         {
             final DatabaseAccessor databaseAccessor = pwmDomain.getPwmApplication().getDatabaseAccessor();
             databaseAccessor.remove( DatabaseTable.OTP, userGUID );
-            LOGGER.info( () -> "cleared OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" );
+            LOGGER.info( pwmRequest, () -> "cleared OTP secret for " + theUser + " in remote database (key=" + userGUID + ")" );
         }
         catch ( final DatabaseException ex )
         {

+ 2 - 2
server/src/main/java/password/pwm/svc/otp/LdapOtpOperator.java

@@ -132,7 +132,7 @@ public class LdapOtpOperator extends AbstractOtpOperator
                     ? pwmDomain.getProxiedChaiUser( null, userIdentity )
                     : pwmRequest.getClientConnectionHolder().getActor( userIdentity );
             theUser.writeStringAttribute( ldapStorageAttribute, value );
-            LOGGER.info( () -> "saved OTP secret for user to chai-ldap format" );
+            LOGGER.info( pwmRequest, () -> "saved OTP secret for user to chai-ldap format" );
         }
         catch ( final ChaiException ex )
         {
@@ -175,7 +175,7 @@ public class LdapOtpOperator extends AbstractOtpOperator
         try
         {
             chaiUser.deleteAttribute( ldapStorageAttribute, null );
-            LOGGER.info( () -> "cleared OTP secret for user to chai-ldap format" );
+            LOGGER.info( pwmRequest, () -> "cleared OTP secret for user to chai-ldap format" );
         }
         catch ( final ChaiOperationException e )
         {

+ 3 - 2
server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java

@@ -25,6 +25,7 @@ import com.blueconic.browscap.ParseException;
 import com.blueconic.browscap.UserAgentParser;
 import com.blueconic.browscap.UserAgentService;
 import lombok.Value;
+import password.pwm.bean.SessionLabel;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -83,11 +84,11 @@ public class UserAgentUtils
         return null;
     }
 
-    public static void initializeCache()
+    public static void initializeCache( final SessionLabel sessionLabel )
     {
         final Instant startTime = Instant.now();
         CACHED_PARSER.get();
-        LOGGER.trace( () -> "loaded useragent parser", TimeDuration.fromCurrent( startTime ) );
+        LOGGER.trace( sessionLabel, () -> "loaded useragent parser", TimeDuration.fromCurrent( startTime ) );
     }
 
     public static void checkIfPreIE11( final PwmRequest pwmRequest ) throws PwmUnrecoverableException

+ 2 - 2
server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -207,11 +207,11 @@ public class TelemetryService extends AbstractPwmService implements PwmService
             }
             catch ( final PwmException e )
             {
-                LOGGER.error( e.getErrorInformation() );
+                LOGGER.error( getSessionLabel(), e.getErrorInformation() );
             }
             catch ( final Exception e )
             {
-                LOGGER.error( () -> "unexpected error during telemetry publish job: " + e.getMessage() );
+                LOGGER.error( getSessionLabel(), () -> "unexpected error during telemetry publish job: " + e.getMessage() );
             }
         }
     }

+ 7 - 5
server/src/main/java/password/pwm/svc/token/DataStoreTokenMachine.java

@@ -100,12 +100,14 @@ public class DataStoreTokenMachine implements TokenMachine
         }
         catch ( final Exception e )
         {
-            LOGGER.error( () -> "unexpected error while cleaning expired stored tokens: " + e.getMessage() );
-        }
-        {
-            final long finalSize = size();
-            LOGGER.trace( () -> "completed record purge cycle; database size = " + finalSize, TimeDuration.fromCurrent( startTime ) );
+            LOGGER.error( tokenService.getSessionLabel(),
+                    () -> "unexpected error while cleaning expired stored tokens: " + e.getMessage() );
         }
+
+        final long finalSize = size();
+        LOGGER.trace( tokenService.getSessionLabel(),
+                () -> "completed record purge cycle; database size = "
+                        + finalSize, TimeDuration.fromCurrent( startTime ) );
     }
 
     private boolean testIfTokenNeedsPurging( final TokenPayload theToken )

+ 18 - 9
server/src/main/java/password/pwm/svc/wordlist/WordlistSource.java

@@ -29,6 +29,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
+import password.pwm.http.PwmURL;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
 import password.pwm.svc.httpclient.PwmHttpClientRequest;
@@ -42,6 +43,7 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URI;
 import java.net.URL;
 import java.time.Instant;
 import java.util.Collections;
@@ -95,7 +97,9 @@ class WordlistSource
         final String importUrl = wordlistConfiguration.getAutoImportUrl();
         return new WordlistSource( WordlistSourceType.AutoImport, importUrl, () ->
         {
-            if ( importUrl.startsWith( "http" ) )
+            final URI uri = URI.create( importUrl );
+
+            if ( PwmURL.uriSchemeMatches( uri, PwmURL.Scheme.http, PwmURL.Scheme.https ) )
             {
                 final boolean promiscuous = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_CLIENT_PROMISCUOUS_WORDLIST_ENABLE ) );
                 final PwmHttpClientConfiguration pwmHttpClientConfiguration = PwmHttpClientConfiguration.builder()
@@ -105,16 +109,21 @@ class WordlistSource
                 return client.streamForUrl( wordlistConfiguration.getAutoImportUrl() );
             }
 
-            try
-            {
-                final URL url = new URL( importUrl );
-                return url.openStream();
-            }
-            catch ( final IOException e )
+            if ( PwmURL.uriSchemeMatches( uri, PwmURL.Scheme.file ) )
             {
-                final String msg = "unable to open auto-import URL: " + e.getMessage();
-                throw PwmUnrecoverableException.newException( PwmError.ERROR_WORDLIST_IMPORT_ERROR, msg );
+                try
+                {
+                    final URL url = URI.create( importUrl ).toURL();
+                    return url.openStream();
+                }
+                catch ( final IOException e )
+                {
+                    final String msg = "unable to open auto-import URL: " + e.getMessage();
+                    throw PwmUnrecoverableException.newException( PwmError.ERROR_WORDLIST_IMPORT_ERROR, msg );
+                }
             }
+
+            throw new IllegalArgumentException( "can't auto-import protocol scheme '" + uri.getScheme() + "'" );
         }
         );
     }

+ 2 - 2
server/src/main/java/password/pwm/user/UserInfoBean.java

@@ -34,7 +34,7 @@ import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.ProfileDefinition;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.http.tag.PasswordRequirementsTag;
+import password.pwm.util.password.PasswordRequirementViewableRuleGenerator;
 import password.pwm.svc.otp.OTPUserRecord;
 import password.pwm.util.macro.MacroRequest;
 
@@ -140,7 +140,7 @@ public class UserInfoBean implements UserInfo
 
         publicUserInfoBean.passwordPolicy( Collections.unmodifiableMap( userInfoBean.getPasswordPolicy().getPolicyMap() ) );
 
-        publicUserInfoBean.passwordRules( PasswordRequirementsTag.getPasswordRequirementsStrings(
+        publicUserInfoBean.passwordRules( PasswordRequirementViewableRuleGenerator.generate(
                 userInfoBean.getPasswordPolicy(),
                 config,
                 locale,

+ 18 - 6
server/src/main/java/password/pwm/util/logging/PwmLogManager.java

@@ -36,6 +36,7 @@ import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
 import ch.qos.logback.core.rolling.RollingFileAppender;
 import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
 import ch.qos.logback.core.util.FileSize;
+import ch.qos.logback.core.util.StatusPrinter;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.slf4j.LoggerFactory;
 import password.pwm.AppProperty;
@@ -49,6 +50,7 @@ import password.pwm.util.localdb.LocalDB;
 import password.pwm.util.localdb.LocalDBException;
 
 import java.io.IOException;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Iterator;
@@ -63,6 +65,10 @@ public class PwmLogManager
     private static final String LOGGER_NAME_FILE = "pwmFileLogger";
     private static final String LOGGER_NAME_CONSOLE = "pwmConsoleLogger";
 
+    private static final String CONTEXT_NAME_PWM_CONFIGURED = PwmConstants.PWM_APP_NAME + "-PwmLogManagerConfigured";
+    static final String CONTEXT_NAME_FILE_CONFIGURED = PwmConstants.PWM_APP_NAME
+            + "-applicationPath-" + PwmConstants.LOGBACK_APP_PATH_FILENAME;
+
     private static final ThreadLocal<SessionLabel> THREAD_SESSION_DATA = new ThreadLocal<>();
 
     private static PwmApplication pwmApplication;
@@ -90,8 +96,8 @@ public class PwmLogManager
             appender.stop();
         }
 
-        logCtx.reset();
         logCtx.stop();
+        logCtx.reset();
 
         PwmLogManager.localDBLogger = null;
         PwmLogManager.pwmApplication = null;
@@ -109,7 +115,7 @@ public class PwmLogManager
     {
         if ( pwmApplicationPath != null )
         {
-            final Path logbackXmlInAppPath = pwmApplicationPath.resolve( "logback.xml" );
+            final Path logbackXmlInAppPath = pwmApplicationPath.resolve( PwmConstants.LOGBACK_APP_PATH_FILENAME );
             if ( Files.exists( logbackXmlInAppPath ) )
             {
                 if ( PwmLogUtil.initLogbackFromXmlFile( logbackXmlInAppPath ) )
@@ -140,7 +146,8 @@ public class PwmLogManager
         initFileLogger( config, pwmLogSettings.getFileLevel(), pwmApplicationPath );
 
         // for debugging
-        // StatusPrinter.print( getLoggerContext() );
+        getLoggerContext().setName( CONTEXT_NAME_PWM_CONFIGURED );
+        StatusPrinter.print( getLoggerContext() );
     }
 
     static PwmLogLevel getLowestLogLevelConfigured()
@@ -184,9 +191,14 @@ public class PwmLogManager
     {
         final LoggerContext context = getLoggerContext();
         final ConfigurationWatchList configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList( context );
-
-        return configurationWatchList != null
-                && ConfigurationWatchListUtil.getConfigurationWatchList( context ).getCopyOfFileWatchList().isEmpty();
+        final URL watchListURL = ConfigurationWatchListUtil.getMainWatchURL( context );
+
+        return watchListURL != null
+                ||
+                (
+                        configurationWatchList != null
+                                && ConfigurationWatchListUtil.getConfigurationWatchList( context ).getCopyOfFileWatchList().isEmpty()
+                );
     }
 
     static LoggerContext getLoggerContext()

+ 3 - 11
server/src/main/java/password/pwm/util/logging/PwmLogUtil.java

@@ -44,8 +44,6 @@ import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
 
-import java.io.IOException;
-import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -126,21 +124,15 @@ class PwmLogUtil
             configurator.setContext( context );
 
             context.reset();
-            try ( InputStream inputStream = Files.newInputStream( file ) )
-            {
-                configurator.doConfigure( inputStream );
-            }
+            configurator.doConfigure( file.toFile() );
+            context.setName( PwmLogManager.CONTEXT_NAME_FILE_CONFIGURED );
 
             return true;
         }
-        catch ( final IOException e  )
-        {
-            /* can't be logged... */
-        }
         catch ( final JoranException je )
         {
             context.reset();
-            // StatusPrinter will handle this
+            System.err.println( "error during logback configuration using file: " + je.getMessage() );
         }
 
         StatusPrinter.printInCaseOfErrorsOrWarnings( context );

+ 0 - 10
server/src/main/java/password/pwm/util/logging/PwmLogger.java

@@ -177,11 +177,6 @@ public class PwmLogger
         doLogEvent( PwmLogLevel.DEBUG, sessionLabel, convertErrorInformation( errorInformation ), null, null );
     }
 
-    public void debug( final Supplier<? extends CharSequence> message, final Throwable exception )
-    {
-        doPwmRequestLogEvent( PwmLogLevel.DEBUG, null, message, exception, null );
-    }
-
     public void info( final Supplier<? extends CharSequence> message )
     {
         doLogEvent( PwmLogLevel.INFO, null, message, null, null );
@@ -237,11 +232,6 @@ public class PwmLogger
         doPwmRequestLogEvent( PwmLogLevel.ERROR, pwmRequest, convertErrorInformation( errorInformation ), null, null );
     }
 
-    public void error( final ErrorInformation errorInformation )
-    {
-        doPwmRequestLogEvent( PwmLogLevel.ERROR, null, convertErrorInformation( errorInformation ), null, null );
-    }
-
     public void error( final SessionLabel sessionLabel, final Supplier<? extends CharSequence> message )
     {
         doLogEvent( PwmLogLevel.ERROR, sessionLabel, message, null, null );

+ 504 - 0
server/src/main/java/password/pwm/util/password/PasswordRequirementViewableRuleGenerator.java

@@ -0,0 +1,504 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.util.password;
+
+import password.pwm.bean.SessionLabel;
+import password.pwm.config.DomainConfig;
+import password.pwm.config.option.ADPolicyComplexity;
+import password.pwm.config.profile.PwmPasswordPolicy;
+import password.pwm.config.profile.PwmPasswordRule;
+import password.pwm.i18n.Display;
+import password.pwm.i18n.Message;
+import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.MissingResourceException;
+
+/**
+ * That is one long class name.  But it does what it says.
+ */
+public class PasswordRequirementViewableRuleGenerator
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( PasswordRequirementViewableRuleGenerator.class );
+
+    private static final String UNKNOWN_MESSAGE_STRING = "UNKNOWN MESSAGE STRING";
+
+    private static final List<RuleTextGenerator> GENERATORS
+            = JavaHelper.instancesOfSealedInterface( RuleTextGenerator.class );
+
+    public static List<String> generate(
+            final PwmPasswordPolicy passwordPolicy,
+            final DomainConfig config,
+            final Locale locale,
+            final MacroRequest macroRequest
+    )
+    {
+        final GeneratorContext policyValues = new GeneratorContext( passwordPolicy, passwordPolicy.ruleHelper(), locale, config, macroRequest );
+
+
+        return GENERATORS.stream()
+                .map( gen -> gen.generate( policyValues ) )
+                .flatMap( Collection::stream )
+                .toList();
+    }
+
+    private sealed interface RuleTextGenerator
+    {
+        List<String> generate( GeneratorContext policyValues );
+    }
+
+    private record GeneratorContext(
+            PwmPasswordPolicy passwordPolicy,
+            PasswordRuleReaderHelper ruleHelper,
+            Locale locale,
+            DomainConfig config,
+            MacroRequest macroRequest
+    )
+    {
+    }
+
+    private static final class CaseSensitiveRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            if ( policyValues.ruleHelper().readBooleanValue( PwmPasswordRule.CaseSensitive ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_CaseSensitive, null, policyValues ) );
+            }
+            else
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_NotCaseSensitive, null, policyValues ) );
+            }
+        }
+    }
+
+    private static final class MinLengthRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumLength );
+            final ADPolicyComplexity adPolicyLevel = policyValues.ruleHelper().getADComplexityLevel();
+
+            if ( adPolicyLevel == ADPolicyComplexity.AD2003 || adPolicyLevel == ADPolicyComplexity.AD2008 )
+            {
+                if ( value < 6 )
+                {
+                    value = 6;
+                }
+            }
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MinLength, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaxLengthRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumLength );
+            if ( value > 0 && value < 64 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxLength, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MinAlphaRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumAlpha );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MinAlpha, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaxAlphaRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumAlpha );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxAlpha, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class NumericCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final PasswordRuleReaderHelper ruleHelper = policyValues.ruleHelper();
+            if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowNumeric ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_AllowNumeric, null, policyValues ) );
+            }
+            else
+            {
+                final List<String> returnValues = new ArrayList<>(  );
+                final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumNumeric );
+                if ( minValue > 0 )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_MinNumeric, minValue, policyValues ) );
+                }
+
+                final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumNumeric );
+                if ( maxValue > 0 )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_MaxNumeric, maxValue, policyValues ) );
+                }
+
+                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharNumeric ) )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_FirstNumeric, maxValue, policyValues ) );
+                }
+
+                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharNumeric ) )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_LastNumeric, maxValue, policyValues ) );
+                }
+                return returnValues;
+            }
+        }
+    }
+
+    private static final class SpecialCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final PasswordRuleReaderHelper ruleHelper = policyValues.ruleHelper();
+            if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowSpecial ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_AllowSpecial, null, policyValues ) );
+            }
+            else
+            {
+                final List<String> returnValues = new ArrayList<>(  );
+                final int minValue = ruleHelper.readIntValue( PwmPasswordRule.MinimumSpecial );
+                if ( minValue > 0 )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_MinSpecial, minValue, policyValues ) );
+                }
+
+                final int maxValue = ruleHelper.readIntValue( PwmPasswordRule.MaximumSpecial );
+                if ( maxValue > 0 )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_MaxSpecial, maxValue, policyValues ) );
+                }
+
+                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowFirstCharSpecial ) )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_FirstSpecial, maxValue, policyValues ) );
+                }
+
+                if ( !ruleHelper.readBooleanValue( PwmPasswordRule.AllowLastCharSpecial ) )
+                {
+                    returnValues.add( getLocalString( Message.Requirement_LastSpecial, maxValue, policyValues ) );
+                }
+                return Collections.unmodifiableList( returnValues );
+            }
+        }
+    }
+
+    private static final class MaximumRepeatRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumRepeat );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxRepeat, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaximumSequentialRepeatRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumSequentialRepeat );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxSeqRepeat, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MinimumLowerRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumLowerCase );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MinLower, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaximumLowerRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumLowerCase );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxLower, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MinimumUpperRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumUpperCase );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MinUpper, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaximumUpperRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumUpperCase );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MaxUpper, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MinimumUniqueRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumUnique );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_MinUnique, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class DisallowedValuesRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final List<String> setValue = policyValues.ruleHelper().getDisallowedValues();
+            if ( !setValue.isEmpty() )
+            {
+                final StringBuilder fieldValue = new StringBuilder();
+                for ( final String loopValue : setValue )
+                {
+                    fieldValue.append( ' ' );
+
+                    final String expandedValue = policyValues.macroRequest().expandMacros( loopValue );
+                    fieldValue.append( StringUtil.escapeHtml( expandedValue ) );
+                }
+                return Collections.singletonList( getLocalString( Message.Requirement_DisAllowedValues, fieldValue.toString(), policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class WordlistRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            if ( policyValues.ruleHelper().readBooleanValue( PwmPasswordRule.EnableWordlist ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_WordList, "", policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class DisallowedAttributesRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final List<String> setValue = policyValues.ruleHelper().getDisallowedAttributes();
+            final ADPolicyComplexity adPolicyLevel = policyValues.ruleHelper().getADComplexityLevel();
+            if ( !setValue.isEmpty() || adPolicyLevel == ADPolicyComplexity.AD2003 )
+            {
+                return Collections.singletonList(  getLocalString( Message.Requirement_DisAllowedAttributes, "", policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MaximumOldCharsRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MaximumOldChars );
+            if ( value > 0 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_OldChar, value, policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class MinimumLifetimeRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final int value = policyValues.ruleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
+
+            if ( value <= 0 )
+            {
+                return Collections.emptyList();
+            }
+
+            final int secondsPerDay = 60 * 60 * 24;
+
+            final String durationStr;
+            if ( value % secondsPerDay == 0 )
+            {
+                final int valueAsDays = value / ( 60 * 60 * 24 );
+                final Display key = valueAsDays <= 1 ? Display.Display_Day : Display.Display_Days;
+                durationStr = valueAsDays + " " + LocaleHelper.getLocalizedMessage( policyValues.locale(), key, policyValues.config() );
+            }
+            else
+            {
+                final int valueAsHours = value / ( 60 * 60 );
+                final Display key = valueAsHours <= 1 ? Display.Display_Hour : Display.Display_Hours;
+                durationStr = valueAsHours + " " + LocaleHelper.getLocalizedMessage( policyValues.locale(), key, policyValues.config() );
+            }
+
+            final String userMsg = Message.getLocalizedMessage( policyValues.locale(), Message.Requirement_MinimumFrequency, policyValues.config(), durationStr );
+            return Collections.singletonList( userMsg );
+        }
+    }
+
+    private static final class ADRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            final ADPolicyComplexity adPolicyLevel = policyValues.ruleHelper().getADComplexityLevel();
+            if ( adPolicyLevel == ADPolicyComplexity.AD2003 )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity, "", policyValues ) );
+            }
+            else if ( adPolicyLevel == ADPolicyComplexity.AD2008 )
+            {
+                final int maxViolations = policyValues.ruleHelper().readIntValue( PwmPasswordRule.ADComplexityMaxViolations );
+                final int minGroups = 5 - maxViolations;
+                return Collections.singletonList( getLocalString( Message.Requirement_ADComplexity2008, String.valueOf( minGroups ), policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static final class UniqueRequiredRuleTextGenerator implements RuleTextGenerator
+    {
+        @Override
+        public List<String> generate( final GeneratorContext policyValues )
+        {
+            if ( policyValues.ruleHelper().readBooleanValue( PwmPasswordRule.UniqueRequired ) )
+            {
+                return Collections.singletonList( getLocalString( Message.Requirement_UniqueRequired, "", policyValues ) );
+            }
+            return Collections.emptyList();
+        }
+    }
+
+    private static String getLocalString( final Message message, final int size, final GeneratorContext policyValues )
+    {
+        final Message effectiveMessage = size > 1 && message.getPluralMessage() != null
+                ? message.getPluralMessage()
+                : message;
+
+        try
+        {
+            return Message.getLocalizedMessage( policyValues.locale(), effectiveMessage, policyValues.config(), String.valueOf( size ) );
+        }
+        catch ( final MissingResourceException e )
+        {
+            LOGGER.error( SessionLabel.SYSTEM_LABEL, () -> "unable to display requirement tag for message '"
+                    + message.toString() + "': " + e.getMessage() );
+        }
+        return UNKNOWN_MESSAGE_STRING;
+    }
+
+    private static String getLocalString( final Message message, final String field, final GeneratorContext policyValues )
+    {
+        try
+        {
+            return Message.getLocalizedMessage( policyValues.locale(), message, policyValues.config(), field );
+        }
+        catch ( final MissingResourceException e )
+        {
+            LOGGER.error(  SessionLabel.SYSTEM_LABEL, () -> "unable to display requirement tag for message '"
+                    + message.toString() + "': " + e.getMessage() );
+        }
+        return UNKNOWN_MESSAGE_STRING;
+    }
+}

+ 3 - 3
server/src/main/java/password/pwm/util/password/PasswordRuleChecks.java

@@ -137,7 +137,7 @@ public class PasswordRuleChecks
                 .pwmDomain( pwmDomain )
                 .policy( policy )
                 .userInfo( userInfo )
-                .ruleHelper( policy.getRuleHelper() )
+                .ruleHelper( policy.ruleHelper() )
                 .macroRequest( macroRequest )
                 .charCounter( new PasswordCharCounter( password ) )
                 .build();
@@ -570,9 +570,9 @@ public class PasswordRuleChecks
             final PwmPasswordPolicy policy = ruleCheckData.getPolicy();
 
             // check disallowed attributes.
-            if ( !policy.getRuleHelper().getDisallowedAttributes().isEmpty() )
+            if ( !policy.ruleHelper().getDisallowedAttributes().isEmpty() )
             {
-                final List<String> paramConfigs = policy.getRuleHelper().getDisallowedAttributes( PasswordRuleReaderHelper.Flag.KeepThresholds );
+                final List<String> paramConfigs = policy.ruleHelper().getDisallowedAttributes( PasswordRuleReaderHelper.Flag.KeepThresholds );
                 if ( userInfo != null )
                 {
                     final Map<String, String> userValues = userInfo.getCachedPasswordRuleAttributes();

+ 3 - 3
server/src/main/java/password/pwm/util/password/PasswordUtility.java

@@ -1065,7 +1065,7 @@ public class PasswordUtility
         int errorCode = 0;
 
         final boolean passwordIsCaseSensitive = userInfo.getPasswordPolicy() == null
-                || userInfo.getPasswordPolicy().getRuleHelper().readBooleanValue( PwmPasswordRule.CaseSensitive );
+                || userInfo.getPasswordPolicy().ruleHelper().readBooleanValue( PwmPasswordRule.CaseSensitive );
 
         final CachePolicy cachePolicy;
         {
@@ -1334,7 +1334,7 @@ public class PasswordUtility
         final Instant lastModified = userInfo.getPasswordLastModifiedTime();
         final TimeDuration minimumLifetime;
         {
-            final int minimumLifetimeSeconds = userInfo.getPasswordPolicy().getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
+            final int minimumLifetimeSeconds = userInfo.getPasswordPolicy().ruleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
             if ( minimumLifetimeSeconds < 1 )
             {
                 return;
@@ -1398,7 +1398,7 @@ public class PasswordUtility
 
         final TimeDuration minimumLifetime;
         {
-            final int minimumLifetimeSeconds = passwordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
+            final int minimumLifetimeSeconds = passwordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLifetime );
             if ( minimumLifetimeSeconds < 1 )
             {
                 return false;

+ 4 - 4
server/src/main/java/password/pwm/util/password/RandomGeneratorConfig.java

@@ -108,7 +108,7 @@ public class RandomGeneratorConfig
         }
         if ( pwmPasswordPolicy != null )
         {
-            policyMax = Math.min( policyMax, pwmPasswordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLength ) );
+            policyMax = Math.min( policyMax, pwmPasswordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MaximumLength ) );
         }
         return policyMax;
     }
@@ -122,10 +122,10 @@ public class RandomGeneratorConfig
         }
         if ( pwmPasswordPolicy != null )
         {
-            final int policyMin = pwmPasswordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLength );
+            final int policyMin = pwmPasswordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLength );
             if ( policyMin > 0 )
             {
-                returnVal = Math.min( returnVal, pwmPasswordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLength ) );
+                returnVal = Math.min( returnVal, pwmPasswordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLength ) );
             }
         }
         return returnVal;
@@ -141,7 +141,7 @@ public class RandomGeneratorConfig
 
         if ( pwmPasswordPolicy != null )
         {
-            policyMin = Math.max( policyMin, pwmPasswordPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumStrength ) );
+            policyMin = Math.max( policyMin, pwmPasswordPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumStrength ) );
         }
         return policyMin;
     }

+ 3 - 3
server/src/main/java/password/pwm/util/password/RandomPasswordGenerator.java

@@ -171,15 +171,15 @@ public class RandomPasswordGenerator
         final Map<String, String> newPolicyMap = new HashMap<>( defaultPolicy.getPolicyMap() );
 
         newPolicyMap.put( PwmPasswordRule.MaximumLength.getKey(), String.valueOf( effectiveConfig.getMaximumLength() ) );
-        if ( effectiveConfig.getMinimumLength() > defaultPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumLength ) )
+        if ( effectiveConfig.getMinimumLength() > defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumLength ) )
         {
             newPolicyMap.put( PwmPasswordRule.MinimumLength.getKey(), String.valueOf( effectiveConfig.getMinimumLength() ) );
         }
-        if ( effectiveConfig.getMaximumLength() < defaultPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MaximumLength ) )
+        if ( effectiveConfig.getMaximumLength() < defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MaximumLength ) )
         {
             newPolicyMap.put( PwmPasswordRule.MaximumLength.getKey(), String.valueOf( effectiveConfig.getMaximumLength() ) );
         }
-        if ( effectiveConfig.getMinimumStrength() > defaultPolicy.getRuleHelper().readIntValue( PwmPasswordRule.MinimumStrength ) )
+        if ( effectiveConfig.getMinimumStrength() > defaultPolicy.ruleHelper().readIntValue( PwmPasswordRule.MinimumStrength ) )
         {
             newPolicyMap.put( PwmPasswordRule.MinimumStrength.getKey(), String.valueOf( effectiveConfig.getMinimumStrength() ) );
         }

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

@@ -114,7 +114,7 @@ public class ConfigurationCleanerTest
         for ( final ProfileID profile : domainConfig.getPasswordProfileIDs() )
         {
             final PwmPasswordPolicy pwmPasswordPolicy = domainConfig.getPasswordPolicy( profile );
-            final ADPolicyComplexity adPolicyComplexity = pwmPasswordPolicy.getRuleHelper().getADComplexityLevel();
+            final ADPolicyComplexity adPolicyComplexity = pwmPasswordPolicy.ruleHelper().getADComplexityLevel();
 
             Assertions.assertEquals( ADPolicyComplexity.AD2003, adPolicyComplexity );
         }