Browse Source

optional/null refactoring

Jason Rivard 4 years ago
parent
commit
949b8f543d
61 changed files with 621 additions and 633 deletions
  1. 1 1
      server/src/main/java/password/pwm/PwmAboutProperty.java
  2. 1 1
      server/src/main/java/password/pwm/config/profile/NewUserProfile.java
  3. 11 8
      server/src/main/java/password/pwm/config/value/ChallengeValue.java
  4. 0 11
      server/src/main/java/password/pwm/config/value/data/ShortcutItem.java
  5. 6 31
      server/src/main/java/password/pwm/error/PwmError.java
  6. 2 1
      server/src/main/java/password/pwm/error/PwmUnrecoverableException.java
  7. 3 3
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  8. 5 2
      server/src/main/java/password/pwm/http/JspUtility.java
  9. 6 5
      server/src/main/java/password/pwm/http/servlet/AbstractPwmServlet.java
  10. 16 43
      server/src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java
  11. 7 13
      server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java
  12. 69 81
      server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java
  13. 7 13
      server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  14. 2 2
      server/src/main/java/password/pwm/http/servlet/activation/ActivateUserServlet.java
  15. 6 5
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  16. 6 11
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerCertificatesServlet.java
  17. 6 13
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLocalDBServlet.java
  18. 6 13
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerLoginServlet.java
  19. 8 14
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java
  20. 14 23
      server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerWordlistServlet.java
  21. 33 25
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  22. 6 6
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStageProcessor.java
  23. 17 10
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordStateMachine.java
  24. 14 22
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  25. 4 4
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  26. 4 3
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserServlet.java
  27. 2 1
      server/src/main/java/password/pwm/http/servlet/newuser/NewUserUtils.java
  28. 2 2
      server/src/main/java/password/pwm/http/servlet/oauth/OAuthConsumerServlet.java
  29. 1 1
      server/src/main/java/password/pwm/http/tag/PwmScriptTag.java
  30. 3 3
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  31. 13 18
      server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java
  32. 1 1
      server/src/main/java/password/pwm/ldap/auth/AuthenticationUtility.java
  33. 13 10
      server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java
  34. 1 1
      server/src/main/java/password/pwm/ldap/auth/SessionAuthenticator.java
  35. 4 2
      server/src/main/java/password/pwm/ldap/search/UserSearchJob.java
  36. 4 3
      server/src/main/java/password/pwm/svc/event/AuditService.java
  37. 2 101
      server/src/main/java/password/pwm/svc/httpclient/ApachePwmHttpClient.java
  38. 51 3
      server/src/main/java/password/pwm/svc/httpclient/JavaPwmHttpClient.java
  39. 4 4
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClient.java
  40. 148 1
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientMessage.java
  41. 12 14
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientRequest.java
  42. 5 12
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientResponse.java
  43. 6 5
      server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java
  44. 3 1
      server/src/main/java/password/pwm/svc/intruder/IntruderRecordManager.java
  45. 23 24
      server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java
  46. 3 2
      server/src/main/java/password/pwm/svc/intruder/StubRecordManager.java
  47. 2 2
      server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java
  48. 4 4
      server/src/main/java/password/pwm/util/form/FormUtility.java
  49. 12 11
      server/src/main/java/password/pwm/util/logging/LocalDBLogger.java
  50. 4 4
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  51. 2 2
      server/src/main/java/password/pwm/util/password/PwmPasswordRuleValidator.java
  52. 2 2
      server/src/main/java/password/pwm/util/secure/SecureEngine.java
  53. 5 6
      server/src/main/java/password/pwm/ws/server/RestUtility.java
  54. 2 2
      server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java
  55. 3 3
      server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java
  56. 5 11
      server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java
  57. 3 3
      server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java
  58. 2 2
      server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java
  59. 8 4
      server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java
  60. 4 3
      server/src/test/java/password/pwm/util/localdb/LocalDBLoggerExtendedTest.java
  61. 2 1
      webapp/src/main/webapp/WEB-INF/jsp/admin-logview.jsp

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

@@ -65,7 +65,7 @@ public enum PwmAboutProperty
     app_smsQueueOldestTime( null, pwmApplication -> format( pwmApplication.getSmsQueue().eldestItem() ) ),
     app_smsQueueOldestTime( null, pwmApplication -> format( pwmApplication.getSmsQueue().eldestItem() ) ),
     app_syslogQueueSize( null, pwmApplication -> Integer.toString( pwmApplication.getAuditService().syslogQueueSize() ) ),
     app_syslogQueueSize( null, pwmApplication -> Integer.toString( pwmApplication.getAuditService().syslogQueueSize() ) ),
     app_localDbLogSize( null, pwmApplication -> Integer.toString( pwmApplication.getLocalDBLogger().getStoredEventCount() ) ),
     app_localDbLogSize( null, pwmApplication -> Integer.toString( pwmApplication.getLocalDBLogger().getStoredEventCount() ) ),
-    app_localDbLogOldestTime( null, pwmApplication -> format( pwmApplication.getLocalDBLogger().getTailDate() ) ),
+    app_localDbLogOldestTime( null, pwmApplication -> format( pwmApplication.getLocalDBLogger().getTailDate().orElse( null ) ) ),
     app_localDbStorageSize( null, pwmApplication -> StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( pwmApplication.getLocalDB().getFileLocation() ) ) ),
     app_localDbStorageSize( null, pwmApplication -> StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( pwmApplication.getLocalDB().getFileLocation() ) ) ),
     app_localDbFreeSpace( null, pwmApplication -> StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( pwmApplication.getLocalDB().getFileLocation() ) ) ),
     app_localDbFreeSpace( null, pwmApplication -> StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( pwmApplication.getLocalDB().getFileLocation() ) ) ),
     app_configurationRestartCounter( null, pwmApplication -> Integer.toString( pwmApplication.getPwmEnvironment().getContextManager().getRestartCount() ) ),
     app_configurationRestartCounter( null, pwmApplication -> Integer.toString( pwmApplication.getPwmEnvironment().getContextManager().getRestartCount() ) ),

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

@@ -146,7 +146,7 @@ public class NewUserProfile extends AbstractProfile implements Profile
                 }
                 }
                 catch ( final ChaiUnavailableException e )
                 catch ( final ChaiUnavailableException e )
                 {
                 {
-                    throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+                    throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
                 }
                 }
             }
             }
         }
         }

+ 11 - 8
server/src/main/java/password/pwm/config/value/ChallengeValue.java

@@ -22,10 +22,12 @@ package password.pwm.config.value;
 
 
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.reflect.TypeToken;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
+import password.pwm.config.stored.StoredConfigXmlConstants;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.value.data.ChallengeItemConfiguration;
 import password.pwm.config.value.data.ChallengeItemConfiguration;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.java.XmlFactory;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
@@ -36,6 +38,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.TreeMap;
 import java.util.TreeMap;
 
 
 public class ChallengeValue extends AbstractValue implements StoredValue
 public class ChallengeValue extends AbstractValue implements StoredValue
@@ -82,9 +85,9 @@ public class ChallengeValue extends AbstractValue implements StoredValue
                     final PwmSecurityKey input
                     final PwmSecurityKey input
             )
             )
             {
             {
-                final List<XmlElement> valueElements = settingElement.getChildren( "value" );
+                final List<XmlElement> valueElements = settingElement.getChildren( StoredConfigXmlConstants.XML_ELEMENT_VALUE );
                 final Map<String, List<ChallengeItemConfiguration>> values = new TreeMap<>();
                 final Map<String, List<ChallengeItemConfiguration>> values = new TreeMap<>();
-                final boolean oldStyle = "LOCALIZED_STRING_ARRAY".equals( settingElement.getAttributeValue( "syntax" ).orElse( "" ) );
+                final boolean oldStyle = "LOCALIZED_STRING_ARRAY".equals( settingElement.getAttributeValue( StoredConfigXmlConstants.XML_ATTRIBUTE_SYNTAX ).orElse( "" ) );
                 for ( final XmlElement loopValueElement : valueElements )
                 for ( final XmlElement loopValueElement : valueElements )
                 {
                 {
                     final String localeString = loopValueElement.getAttributeValue( "locale" ).orElse( "" );
                     final String localeString = loopValueElement.getAttributeValue( "locale" ).orElse( "" );
@@ -93,7 +96,7 @@ public class ChallengeValue extends AbstractValue implements StoredValue
                         final ChallengeItemConfiguration challengeItemBean;
                         final ChallengeItemConfiguration challengeItemBean;
                         if ( oldStyle )
                         if ( oldStyle )
                         {
                         {
-                            challengeItemBean = parseOldVersionString( value );
+                            challengeItemBean = parseOldVersionString( value ).orElse( null );
                         }
                         }
                         else
                         else
                         {
                         {
@@ -187,13 +190,13 @@ public class ChallengeValue extends AbstractValue implements StoredValue
         return Collections.emptyList();
         return Collections.emptyList();
     }
     }
 
 
-    private static ChallengeItemConfiguration parseOldVersionString(
+    private static Optional<ChallengeItemConfiguration> parseOldVersionString(
             final String inputString
             final String inputString
     )
     )
     {
     {
-        if ( inputString == null || inputString.length() < 1 )
+        if ( StringUtil.isEmpty( inputString ) )
         {
         {
-            return null;
+            return Optional.empty();
         }
         }
 
 
         int minLength = 2;
         int minLength = 2;
@@ -235,12 +238,12 @@ public class ChallengeValue extends AbstractValue implements StoredValue
             adminDefined = false;
             adminDefined = false;
         }
         }
 
 
-        return ChallengeItemConfiguration.builder()
+        return Optional.of( ChallengeItemConfiguration.builder()
                 .text( challengeText )
                 .text( challengeText )
                 .minLength( minLength )
                 .minLength( minLength )
                 .maxLength( maxLength )
                 .maxLength( maxLength )
                 .adminDefined( adminDefined )
                 .adminDefined( adminDefined )
-                .build();
+                .build() );
     }
     }
 
 
     @Override
     @Override

+ 0 - 11
server/src/main/java/password/pwm/config/value/data/ShortcutItem.java

@@ -29,7 +29,6 @@ import java.net.URI;
 @Value
 @Value
 public class ShortcutItem implements Serializable
 public class ShortcutItem implements Serializable
 {
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( ShortcutItem.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( ShortcutItem.class );
 
 
     private final String label;
     private final String label;
@@ -37,16 +36,6 @@ public class ShortcutItem implements Serializable
     private final String ldapQuery;
     private final String ldapQuery;
     private final String description;
     private final String description;
 
 
-    public String toString( )
-    {
-        return "ShortcutItem{"
-                + "label='" + label + '\''
-                + ", shortcutURI=" + shortcutURI
-                + ", ldapQuery='" + ldapQuery + '\''
-                + ", description='" + description + '\''
-                + '}';
-    }
-
     public static ShortcutItem parsePwmConfigInput( final String input )
     public static ShortcutItem parsePwmConfigInput( final String input )
     {
     {
         if ( input != null && input.length() > 0 )
         if ( input != null && input.length() > 0 )

+ 6 - 31
server/src/main/java/password/pwm/error/PwmError.java

@@ -27,6 +27,7 @@ import password.pwm.util.java.JavaHelper;
 
 
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Locale;
 import java.util.Locale;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
@@ -384,41 +385,15 @@ public enum PwmError
         return LocaleHelper.getLocalizedMessage( locale, this.getResourceKey(), config, password.pwm.i18n.Error.class, fieldValue );
         return LocaleHelper.getLocalizedMessage( locale, this.getResourceKey(), config, password.pwm.i18n.Error.class, fieldValue );
     }
     }
 
 
-    public static PwmError forChaiError( final ChaiError errorCode )
+    public static Optional<PwmError> forChaiError( final ChaiError errorCode )
     {
     {
-        if ( errorCode == null )
-        {
-            return null;
-        }
-
-        for ( final PwmError pwmError : values() )
-        {
-            if ( pwmError.chaiErrorCode != null )
-            {
-                for ( final ChaiError loopCode : pwmError.chaiErrorCode )
-                {
-                    if ( loopCode == errorCode )
-                    {
-                        return pwmError;
-                    }
-                }
-            }
-        }
-
-        return null;
+        return JavaHelper.readEnumFromPredicate( PwmError.class,
+                pwmError -> pwmError.chaiErrorCode.contains( errorCode ) );
     }
     }
 
 
-    public static PwmError forErrorNumber( final int code )
+    public static Optional<PwmError> forErrorNumber( final int code )
     {
     {
-        for ( final PwmError pwmError : values() )
-        {
-            if ( pwmError.getErrorCode() == code )
-            {
-                return pwmError;
-            }
-        }
-
-        return null;
+        return JavaHelper.readEnumFromPredicate( PwmError.class, pwmError -> pwmError.getErrorCode() == code );
     }
     }
 
 
     public boolean isTrivial()
     public boolean isTrivial()

+ 2 - 1
server/src/main/java/password/pwm/error/PwmUnrecoverableException.java

@@ -61,7 +61,8 @@ public class PwmUnrecoverableException extends PwmException
         }
         }
         else
         else
         {
         {
-            errorInformation = new ErrorInformation( PwmError.forChaiError( e.getErrorCode() ), e.getMessage() );
+            errorInformation = new ErrorInformation( PwmError.forChaiError( e.getErrorCode() )
+                    .orElse( PwmError.ERROR_INTERNAL ), e.getMessage() );
         }
         }
         return new PwmUnrecoverableException( errorInformation );
         return new PwmUnrecoverableException( errorInformation );
     }
     }

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

@@ -562,15 +562,15 @@ public class LDAPHealthChecker implements HealthSupplier
             catch ( final ChaiException e )
             catch ( final ChaiException e )
             {
             {
                 final ChaiError chaiError = ChaiErrors.getErrorForMessage( e.getMessage() );
                 final ChaiError chaiError = ChaiErrors.getErrorForMessage( e.getMessage() );
-                final PwmError pwmError = PwmError.forChaiError( chaiError );
+                final PwmError pwmError = PwmError.forChaiError( chaiError ).orElse( PwmError.ERROR_INTERNAL );
                 final StringBuilder errorString = new StringBuilder();
                 final StringBuilder errorString = new StringBuilder();
                 final String profileName = ldapProfile.getIdentifier();
                 final String profileName = ldapProfile.getIdentifier();
                 errorString.append( "error connecting to ldap directory (" ).append( profileName ).append( "), error: " ).append( e.getMessage() );
                 errorString.append( "error connecting to ldap directory (" ).append( profileName ).append( "), error: " ).append( e.getMessage() );
                 if ( chaiError != null && chaiError != ChaiError.UNKNOWN )
                 if ( chaiError != null && chaiError != ChaiError.UNKNOWN )
                 {
                 {
                     errorString.append( " (" );
                     errorString.append( " (" );
-                    errorString.append( chaiError.toString() );
-                    if ( pwmError != null && pwmError != PwmError.ERROR_INTERNAL )
+                    errorString.append( chaiError );
+                    if ( pwmError != PwmError.ERROR_INTERNAL )
                     {
                     {
                         errorString.append( " - " );
                         errorString.append( " - " );
                         errorString.append( pwmError.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, pwmDomain.getConfig() ) );
                         errorString.append( pwmError.getLocalizedMessage( PwmConstants.DEFAULT_LOCALE, pwmDomain.getConfig() ) );

+ 5 - 2
server/src/main/java/password/pwm/http/JspUtility.java

@@ -22,6 +22,7 @@ package password.pwm.http;
 
 
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
+import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.http.bean.PwmSessionBean;
 import password.pwm.i18n.PwmDisplayBundle;
 import password.pwm.i18n.PwmDisplayBundle;
@@ -56,6 +57,7 @@ public abstract class JspUtility
     }
     }
 
 
     public static <E extends PwmSessionBean> E getSessionBean( final PageContext pageContext, final Class<E> theClass )
     public static <E extends PwmSessionBean> E getSessionBean( final PageContext pageContext, final Class<E> theClass )
+            throws PwmUnrecoverableException
     {
     {
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         final PwmRequest pwmRequest = forRequest( pageContext.getRequest() );
         try
         try
@@ -64,9 +66,10 @@ public abstract class JspUtility
         }
         }
         catch ( final PwmUnrecoverableException e )
         catch ( final PwmUnrecoverableException e )
         {
         {
-            LOGGER.warn( () -> "unable to load pwmRequest object during jsp execution: " + e.getMessage() );
+            final String msg = "unable to load pwmRequest object during jsp execution: " + e.getMessage();
+            LOGGER.warn( () -> msg );
+            throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, msg );
         }
         }
-        return null;
     }
     }
 
 
     public static Serializable getAttribute( final PageContext pageContext, final PwmRequestAttribute requestAttr )
     public static Serializable getAttribute( final PageContext pageContext, final PwmRequestAttribute requestAttr )

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

@@ -49,6 +49,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.io.StringWriter;
 import java.util.Collection;
 import java.util.Collection;
+import java.util.Optional;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
 public abstract class AbstractPwmServlet extends HttpServlet implements PwmServlet
 public abstract class AbstractPwmServlet extends HttpServlet implements PwmServlet
@@ -110,13 +111,13 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
             }
             }
 
 
             // check for incorrect method type.
             // check for incorrect method type.
-            final ProcessAction processAction = readProcessAction( pwmRequest );
-            if ( processAction != null )
+            final Optional<? extends ProcessAction> processAction = readProcessAction( pwmRequest );
+            if ( processAction.isPresent() )
             {
             {
-                if ( !processAction.permittedMethods().contains( method ) )
+                if ( !processAction.get().permittedMethods().contains( method ) )
                 {
                 {
                     final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE,
                     final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_SERVICE_NOT_AVAILABLE,
-                            "incorrect request method " + method.toString() + " on request to " + pwmRequest.getURLwithQueryString() );
+                            "incorrect request method " + method + " on request to " + pwmRequest.getURLwithQueryString() );
                     LOGGER.error( pwmRequest, errorInformation::toDebugStr );
                     LOGGER.error( pwmRequest, errorInformation::toDebugStr );
                     pwmRequest.respondWithError( errorInformation, false );
                     pwmRequest.respondWithError( errorInformation, false );
                     return;
                     return;
@@ -289,7 +290,7 @@ public abstract class AbstractPwmServlet extends HttpServlet implements PwmServl
     protected abstract void processAction( PwmRequest request )
     protected abstract void processAction( PwmRequest request )
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException;
             throws ServletException, IOException, ChaiUnavailableException, PwmUnrecoverableException;
 
 
-    protected abstract ProcessAction readProcessAction( PwmRequest request )
+    protected abstract Optional<? extends ProcessAction> readProcessAction( PwmRequest request )
             throws PwmUnrecoverableException;
             throws PwmUnrecoverableException;
 
 
     public interface ProcessAction
     public interface ProcessAction

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

@@ -39,34 +39,15 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
 public abstract class ControlledPwmServlet extends AbstractPwmServlet implements PwmServlet
 public abstract class ControlledPwmServlet extends AbstractPwmServlet implements PwmServlet
 {
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( AbstractPwmServlet.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( AbstractPwmServlet.class );
 
 
-    private final Map<String, Method> actionMethodCache = createMethodCache();
-
-    @Override
-    public String servletUriRemainder( final PwmRequest pwmRequest, final String command ) throws PwmUnrecoverableException
-    {
-        String uri = pwmRequest.getURLwithoutQueryString();
-        if ( uri.startsWith( pwmRequest.getBasePath() ) )
-        {
-            uri = uri.substring( pwmRequest.getBasePath().length() );
-        }
-        for ( final String servletUri : getServletDefinition().urlPatterns() )
-        {
-            if ( uri.startsWith( servletUri ) )
-            {
-                uri = uri.substring( servletUri.length() );
-            }
-        }
-        return uri;
-    }
+    private final Map<? extends ProcessAction, Method> actionMethodCache = createMethodCache();
 
 
     @Override
     @Override
     protected PwmServletDefinition getServletDefinition( )
     protected PwmServletDefinition getServletDefinition( )
@@ -85,21 +66,11 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
     public abstract Class<? extends ProcessAction> getProcessActionsClass( );
     public abstract Class<? extends ProcessAction> getProcessActionsClass( );
 
 
     @Override
     @Override
-    protected ProcessAction readProcessAction( final PwmRequest request )
+    protected Optional<? extends ProcessAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            final String inputParameter = request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST );
-            final Class processStatusClass = getProcessActionsClass();
-            final Enum answer = JavaHelper.readEnumFromString( processStatusClass, null, inputParameter );
-            return ( ProcessAction ) answer;
-        }
-        catch ( final Exception e )
-        {
-            LOGGER.error( () -> "error", e );
-        }
-        return null;
+        final Class processStatusClass = getProcessActionsClass();
+        return JavaHelper.readEnumFromString( processStatusClass,  request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     private ProcessStatus dispatchMethod(
     private ProcessStatus dispatchMethod(
@@ -108,14 +79,14 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
 
 
-        final ProcessAction action = readProcessAction( pwmRequest );
-        if ( action == null )
+        final Optional<? extends ProcessAction> action = readProcessAction( pwmRequest );
+        if ( action.isEmpty() )
         {
         {
             return ProcessStatus.Continue;
             return ProcessStatus.Continue;
         }
         }
         try
         try
         {
         {
-            final Method interestedMethod = actionMethodCache.get( action.toString() );
+            final Method interestedMethod = actionMethodCache.get( action.get() );
             if ( interestedMethod != null )
             if ( interestedMethod != null )
             {
             {
                 interestedMethod.setAccessible( true );
                 interestedMethod.setAccessible( true );
@@ -157,8 +128,8 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
     {
     {
         preProcessCheck( pwmRequest );
         preProcessCheck( pwmRequest );
 
 
-        final ProcessAction action = readProcessAction( pwmRequest );
-        if ( action != null )
+        final Optional<? extends ProcessAction> action = readProcessAction( pwmRequest );
+        if ( action.isPresent() )
         {
         {
             final ProcessStatus status = dispatchMethod( pwmRequest );
             final ProcessStatus status = dispatchMethod( pwmRequest );
             if ( status == ProcessStatus.Halt )
             if ( status == ProcessStatus.Halt )
@@ -215,20 +186,22 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         String action( );
         String action( );
             }
             }
 
 
-    private Map<String, Method> createMethodCache()
+    private Map<? extends ProcessAction, Method> createMethodCache()
     {
     {
-        final Map<String, Method> map = new HashMap<>();
+        final Map<ProcessAction, Method> map = new HashMap<>();
         final Collection<Method> methods = JavaHelper.getAllMethodsForClass( this.getClass() );
         final Collection<Method> methods = JavaHelper.getAllMethodsForClass( this.getClass() );
         for ( final Method method : methods )
         for ( final Method method : methods )
         {
         {
             if ( method.getAnnotation( ActionHandler.class ) != null )
             if ( method.getAnnotation( ActionHandler.class ) != null )
             {
             {
                 final String actionName = method.getAnnotation( ActionHandler.class ).action();
                 final String actionName = method.getAnnotation( ActionHandler.class ).action();
-                map.put( actionName, method );
+                final Class processActionClass = getProcessActionsClass();
+                final Optional<? extends ProcessAction> processAction = JavaHelper.readEnumFromString( processActionClass, actionName );
+                processAction.ifPresent( action -> map.put( action, method ) );
 
 
             }
             }
         }
         }
-        return Collections.unmodifiableMap( map );
+        return Map.copyOf( map );
     }
     }
 }
 }
 
 

+ 7 - 13
server/src/main/java/password/pwm/http/servlet/ForgottenUsernameServlet.java

@@ -61,6 +61,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
 @WebServlet(
 @WebServlet(
         name = "ForgottenUsernameServlet",
         name = "ForgottenUsernameServlet",
@@ -86,17 +87,10 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ForgottenUsernameAction readProcessAction( final PwmRequest request )
+    protected Optional<ForgottenUsernameAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ForgottenUsernameAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ForgottenUsernameAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     @Override
     @Override
@@ -111,19 +105,19 @@ public class ForgottenUsernameServlet extends AbstractPwmServlet
             return;
             return;
         }
         }
 
 
-        final ForgottenUsernameAction action = readProcessAction( pwmRequest );
+        final Optional<ForgottenUsernameAction> action = readProcessAction( pwmRequest );
 
 
-        if ( action != null )
+        if ( action.isPresent() )
         {
         {
             pwmRequest.validatePwmFormID();
             pwmRequest.validatePwmFormID();
-            switch ( action )
+            switch ( action.get() )
             {
             {
                 case search:
                 case search:
                     handleSearchRequest( pwmRequest );
                     handleSearchRequest( pwmRequest );
                     return;
                     return;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( action );
+                    JavaHelper.unhandledSwitchStatement( action.get() );
             }
             }
         }
         }
 
 

+ 69 - 81
server/src/main/java/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -41,6 +41,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.JspUrl;
 import password.pwm.http.JspUrl;
+import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.PwmSession;
@@ -56,7 +57,6 @@ import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.FormMap;
 import password.pwm.util.FormMap;
 import password.pwm.util.PasswordData;
 import password.pwm.util.PasswordData;
 import password.pwm.util.form.FormUtility;
 import password.pwm.util.form.FormUtility;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.PwmDateFormat;
 import password.pwm.util.java.PwmDateFormat;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.macro.MacroRequest;
 import password.pwm.util.macro.MacroRequest;
@@ -77,6 +77,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
@@ -92,7 +93,7 @@ import java.util.Set;
                 PwmConstants.URL_PREFIX_PRIVATE + "/GuestRegistration",
                 PwmConstants.URL_PREFIX_PRIVATE + "/GuestRegistration",
         }
         }
 )
 )
-public class GuestRegistrationServlet extends AbstractPwmServlet
+public class GuestRegistrationServlet extends ControlledPwmServlet
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( GuestRegistrationServlet.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( GuestRegistrationServlet.class );
 
 
@@ -120,81 +121,67 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected GuestRegistrationAction readProcessAction( final PwmRequest request )
-            throws PwmUnrecoverableException
+    public Class<? extends ProcessAction> getProcessActionsClass()
     {
     {
-        try
-        {
-            return GuestRegistrationAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return GuestRegistrationAction.class;
     }
     }
 
 
+    @Override
+    protected PwmServletDefinition getServletDefinition()
+    {
+        return PwmServletDefinition.GuestRegistration;
+    }
 
 
     @Override
     @Override
-    protected void processAction( final PwmRequest pwmRequest )
-            throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
+    public ProcessStatus preProcessCheck( final PwmRequest pwmRequest ) throws PwmUnrecoverableException, IOException, ServletException
     {
     {
-        //Fetch the session state bean.
-        final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
-        final GuestRegistrationBean guestRegistrationBean = pwmDomain.getSessionStateService().getBean( pwmRequest, GuestRegistrationBean.class );
 
 
-        final DomainConfig config = pwmDomain.getConfig();
-
-        if ( !config.readSettingAsBoolean( PwmSetting.GUEST_ENABLE ) )
+        if ( !pwmDomain.getConfig().readSettingAsBoolean( PwmSetting.GUEST_ENABLE ) )
         {
         {
-            pwmRequest.respondWithError( PwmError.ERROR_SERVICE_NOT_AVAILABLE.toInfo() );
-            return;
+            throw new PwmUnrecoverableException( new ErrorInformation(
+                    PwmError.ERROR_SERVICE_NOT_AVAILABLE,
+                    "Setting "
+                            + PwmSetting.GUEST_ENABLE.toMenuLocationDebug( null, null ) + " is not enabled." )
+            );
         }
         }
 
 
-        if ( !pwmSession.getSessionManager().checkPermission( pwmDomain, Permission.GUEST_REGISTRATION ) )
+        if ( !pwmRequest.getPwmSession().getSessionManager().checkPermission( pwmDomain, Permission.GUEST_REGISTRATION ) )
         {
         {
-            pwmRequest.respondWithError( PwmError.ERROR_UNAUTHORIZED.toInfo() );
-            return;
+            throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_UNAUTHORIZED ) );
         }
         }
 
 
-        checkConfiguration( config );
-
-        final GuestRegistrationAction action = readProcessAction( pwmRequest );
-        if ( action != null )
-        {
-            pwmRequest.validatePwmFormID();
-            switch ( action )
-            {
-                case create:
-                    handleCreateRequest( pwmRequest, guestRegistrationBean );
-                    return;
-
-                case search:
-                    handleSearchRequest( pwmRequest, guestRegistrationBean );
-                    return;
+        checkConfiguration( pwmDomain.getConfig() );
 
 
-                case update:
-                    handleUpdateRequest( pwmRequest, guestRegistrationBean );
-                    return;
+        return ProcessStatus.Continue;
+    }
 
 
-                case selectPage:
-                    handleSelectPageRequest( pwmRequest, guestRegistrationBean );
-                    return;
+    @Override
+    protected Optional<? extends ProcessAction> readProcessAction( final PwmRequest request )
+            throws PwmUnrecoverableException
+    {
+        return super.readProcessAction( request );
+    }
 
 
-                default:
-                    JavaHelper.unhandledSwitchStatement( action );
-            }
-        }
+    @Override
+    protected void nextStep( final PwmRequest pwmRequest )
+            throws PwmUnrecoverableException, IOException, ChaiUnavailableException, ServletException
+    {
+        this.forwardToJSP( pwmRequest );
+    }
 
 
-        this.forwardToJSP( pwmRequest, guestRegistrationBean );
+    private GuestRegistrationBean getBean( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    {
+        return pwmRequest.getPwmDomain().getSessionStateService().getBean( pwmRequest, GuestRegistrationBean.class );
     }
     }
 
 
-    protected void handleSelectPageRequest(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+    @ActionHandler( action = "selectPage" )
+    protected ProcessStatus handleSelectPageRequest(
+            final PwmRequest pwmRequest
     )
     )
             throws PwmUnrecoverableException, IOException, ServletException
             throws PwmUnrecoverableException, IOException, ServletException
     {
     {
+        final GuestRegistrationBean guestRegistrationBean = getBean( pwmRequest );
         final String requestedPage = pwmRequest.readParameterAsString( "page" );
         final String requestedPage = pwmRequest.readParameterAsString( "page" );
         try
         try
         {
         {
@@ -204,18 +191,17 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
         {
         {
             LOGGER.error( pwmRequest, () -> "unknown page select request: " + requestedPage );
             LOGGER.error( pwmRequest, () -> "unknown page select request: " + requestedPage );
         }
         }
-        this.forwardToJSP( pwmRequest, guestRegistrationBean );
+        return ProcessStatus.Continue;
     }
     }
 
 
-
-    protected void handleUpdateRequest(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+    @ActionHandler( action = "update" )
+    protected ProcessStatus handleUpdateRequest(
+            final PwmRequest pwmRequest
 
 
     )
     )
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
     {
     {
-        //Fetch the session state bean.
+        final GuestRegistrationBean guestRegistrationBean = getBean( pwmRequest );
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
         final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
@@ -270,7 +256,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
 
 
             //everything good so forward to confirmation page.
             //everything good so forward to confirmation page.
             pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UpdateGuest );
             pwmRequest.getPwmResponse().forwardToSuccessPage( Message.Success_UpdateGuest );
-            return;
+            return ProcessStatus.Halt;
         }
         }
         catch ( final PwmOperationalException e )
         catch ( final PwmOperationalException e )
         {
         {
@@ -283,7 +269,8 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
             LOGGER.error( pwmRequest, info );
             LOGGER.error( pwmRequest, info );
             setLastError( pwmRequest, info );
             setLastError( pwmRequest, info );
         }
         }
-        this.forwardToUpdateJSP( pwmRequest, guestRegistrationBean );
+        this.forwardToUpdateJSP( pwmRequest );
+        return ProcessStatus.Halt;
     }
     }
 
 
     private void sendUpdateGuestEmailConfirmation(
     private void sendUpdateGuestEmailConfirmation(
@@ -305,9 +292,9 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
         pwmRequest.getPwmApplication().getEmailQueue().submitEmail( configuredEmailSetting, guestUserInfo, null );
         pwmRequest.getPwmApplication().getEmailQueue().submitEmail( configuredEmailSetting, guestUserInfo, null );
     }
     }
 
 
-    protected void handleSearchRequest(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+    @ActionHandler( action = "search" )
+    protected ProcessStatus handleSearchRequest(
+            final PwmRequest pwmRequest
     )
     )
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
             throws ServletException, ChaiUnavailableException, IOException, PwmUnrecoverableException
     {
     {
@@ -365,7 +352,7 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
                             final ErrorInformation info = new ErrorInformation( PwmError.ERROR_ORIG_ADMIN_ONLY );
                             final ErrorInformation info = new ErrorInformation( PwmError.ERROR_ORIG_ADMIN_ONLY );
                             setLastError( pwmRequest, info );
                             setLastError( pwmRequest, info );
                             LOGGER.error( pwmRequest, info );
                             LOGGER.error( pwmRequest, info );
-                            this.forwardToJSP( pwmRequest, guestRegistrationBean );
+                            this.forwardToJSP( pwmRequest );
                         }
                         }
                     }
                     }
                 }
                 }
@@ -391,8 +378,8 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
 
 
                 guBean.setUpdateUserIdentity( theGuest );
                 guBean.setUpdateUserIdentity( theGuest );
 
 
-                this.forwardToUpdateJSP( pwmRequest, guestRegistrationBean );
-                return;
+                this.forwardToUpdateJSP( pwmRequest );
+                return ProcessStatus.Halt;
             }
             }
             catch ( final PwmUnrecoverableException e )
             catch ( final PwmUnrecoverableException e )
             {
             {
@@ -403,16 +390,16 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
         {
         {
             final ErrorInformation error = e.getErrorInformation();
             final ErrorInformation error = e.getErrorInformation();
             setLastError( pwmRequest, error );
             setLastError( pwmRequest, error );
-            this.forwardToJSP( pwmRequest, guestRegistrationBean );
-            return;
+            this.forwardToJSP( pwmRequest );
+            return ProcessStatus.Halt;
         }
         }
-        this.forwardToJSP( pwmRequest, guestRegistrationBean );
+        this.forwardToJSP( pwmRequest );
+        return ProcessStatus.Halt;
     }
     }
 
 
-
-    private void handleCreateRequest(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+    @ActionHandler( action = "create" )
+    private ProcessStatus handleCreateRequest(
+            final PwmRequest pwmRequest
     )
     )
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
             throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
     {
     {
@@ -519,14 +506,15 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
             final ErrorInformation info = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, "error creating user: " + e.getMessage() );
             final ErrorInformation info = new ErrorInformation( PwmError.ERROR_NEW_USER_FAILURE, "error creating user: " + e.getMessage() );
             setLastError( pwmRequest, info );
             setLastError( pwmRequest, info );
             LOGGER.error( pwmRequest, info );
             LOGGER.error( pwmRequest, info );
-            this.forwardToJSP( pwmRequest, guestRegistrationBean );
+            this.forwardToJSP( pwmRequest );
         }
         }
         catch ( final PwmOperationalException e )
         catch ( final PwmOperationalException e )
         {
         {
             LOGGER.error( pwmRequest, () -> e.getErrorInformation().toDebugStr() );
             LOGGER.error( pwmRequest, () -> e.getErrorInformation().toDebugStr() );
             setLastError( pwmRequest, e.getErrorInformation() );
             setLastError( pwmRequest, e.getErrorInformation() );
-            this.forwardToJSP( pwmRequest, guestRegistrationBean );
+            this.forwardToJSP( pwmRequest );
         }
         }
+        return ProcessStatus.Halt;
     }
     }
 
 
     private static Instant readExpirationFromRequest(
     private static Instant readExpirationFromRequest(
@@ -624,11 +612,11 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
     }
     }
 
 
     private void forwardToJSP(
     private void forwardToJSP(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+            final PwmRequest pwmRequest
     )
     )
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
+        final GuestRegistrationBean guestRegistrationBean = getBean( pwmRequest );
         calculateFutureDateFlags( pwmRequest, guestRegistrationBean );
         calculateFutureDateFlags( pwmRequest, guestRegistrationBean );
         if ( Page.search == guestRegistrationBean.getCurrentPage() )
         if ( Page.search == guestRegistrationBean.getCurrentPage() )
         {
         {
@@ -643,11 +631,11 @@ public class GuestRegistrationServlet extends AbstractPwmServlet
     }
     }
 
 
     private void forwardToUpdateJSP(
     private void forwardToUpdateJSP(
-            final PwmRequest pwmRequest,
-            final GuestRegistrationBean guestRegistrationBean
+            final PwmRequest pwmRequest
     )
     )
             throws IOException, ServletException, PwmUnrecoverableException
             throws IOException, ServletException, PwmUnrecoverableException
     {
     {
+        final GuestRegistrationBean guestRegistrationBean = getBean( pwmRequest );
         calculateFutureDateFlags( pwmRequest, guestRegistrationBean );
         calculateFutureDateFlags( pwmRequest, guestRegistrationBean );
         final List<FormConfiguration> guestUpdateForm = pwmRequest.getDomainConfig().readSettingAsForm( PwmSetting.GUEST_UPDATE_FORM );
         final List<FormConfiguration> guestUpdateForm = pwmRequest.getDomainConfig().readSettingAsForm( PwmSetting.GUEST_UPDATE_FORM );
         final Map<FormConfiguration, String> formValueMap = new LinkedHashMap<>();
         final Map<FormConfiguration, String> formValueMap = new LinkedHashMap<>();

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

@@ -53,6 +53,7 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 @WebServlet(
 @WebServlet(
@@ -79,17 +80,10 @@ public class ShortcutServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ShortcutAction readProcessAction( final PwmRequest request )
+    protected Optional<ShortcutAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ShortcutAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ShortcutAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     @Override
     @Override
@@ -116,18 +110,18 @@ public class ShortcutServlet extends AbstractPwmServlet
             LOGGER.trace( pwmRequest, () -> "using cashed shortcut values" );
             LOGGER.trace( pwmRequest, () -> "using cashed shortcut values" );
         }
         }
 
 
-        final ShortcutAction action = readProcessAction( pwmRequest );
-        if ( action != null )
+        final Optional<ShortcutAction> action = readProcessAction( pwmRequest );
+        if ( action.isPresent() )
         {
         {
             pwmRequest.validatePwmFormID();
             pwmRequest.validatePwmFormID();
-            switch ( action )
+            switch ( action.get() )
             {
             {
                 case selectShortcut:
                 case selectShortcut:
                     handleUserSelection( pwmRequest, shortcutsBean );
                     handleUserSelection( pwmRequest, shortcutsBean );
                     return;
                     return;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( action );
+                    JavaHelper.unhandledSwitchStatement( action.get() );
             }
             }
         }
         }
 
 

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

@@ -146,10 +146,10 @@ public class ActivateUserServlet extends ControlledPwmServlet
             throw new PwmUnrecoverableException( errorInformation );
             throw new PwmUnrecoverableException( errorInformation );
         }
         }
 
 
-        final ProcessAction action = this.readProcessAction( pwmRequest );
+        final Optional<? extends ProcessAction> action = this.readProcessAction( pwmRequest );
 
 
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
-        if ( action == null )
+        if ( action.isEmpty() )
         {
         {
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.ActivateUser, ActivateUserAction.enterCode ) )
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.ActivateUser, ActivateUserAction.enterCode ) )
             {
             {

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

@@ -65,6 +65,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.TreeMap;
 import java.util.TreeMap;
 
 
 
 
@@ -363,9 +364,9 @@ public class AppDashboardData implements Serializable
                 pwmDomain.getAuditService().sizeToDebugString()
                 pwmDomain.getAuditService().sizeToDebugString()
         ) );
         ) );
         {
         {
-            final Instant eldestAuditRecord = pwmDomain.getAuditService().eldestVaultRecord();
-            final String display = eldestAuditRecord != null
-                    ? TimeDuration.fromCurrent( eldestAuditRecord ).asLongString()
+            final Optional<Instant> eldestAuditRecord = pwmDomain.getAuditService().eldestVaultRecord();
+            final String display = eldestAuditRecord.isPresent()
+                    ? TimeDuration.fromCurrent( eldestAuditRecord.get() ).asLongString()
                     : notApplicable;
                     : notApplicable;
             localDbInfo.add( new DisplayElement(
             localDbInfo.add( new DisplayElement(
                     "oldestLocalAuditRecords",
                     "oldestLocalAuditRecords",
@@ -383,8 +384,8 @@ public class AppDashboardData implements Serializable
         {
         {
             final LocalDBLogger localDBLogger = pwmDomain.getPwmApplication().getLocalDBLogger();
             final LocalDBLogger localDBLogger = pwmDomain.getPwmApplication().getLocalDBLogger();
             final String display = localDBLogger != null
             final String display = localDBLogger != null
-                    && localDBLogger.getTailDate() != null
-                    ? TimeDuration.fromCurrent( localDBLogger.getTailDate() ).asLongString()
+                    && localDBLogger.getTailDate().isPresent()
+                    ? TimeDuration.fromCurrent( localDBLogger.getTailDate().get() ).asLongString()
                     : notApplicable;
                     : notApplicable;
             localDbInfo.add( new DisplayElement(
             localDbInfo.add( new DisplayElement(
                     "oldestLogEvents",
                     "oldestLogEvents",

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

@@ -38,6 +38,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.CollectionUtil;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.X509Utils;
 import password.pwm.util.secure.X509Utils;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.RestResultBean;
@@ -54,6 +55,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Comparator;
 import java.util.List;
 import java.util.List;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 @WebServlet(
 @WebServlet(
@@ -85,17 +87,10 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ConfigManagerCertificateAction readProcessAction( final PwmRequest request )
+    protected Optional<ConfigManagerCertificateAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ConfigManagerCertificateAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ConfigManagerCertificateAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     @Override
     @Override
@@ -104,10 +99,10 @@ public class ConfigManagerCertificatesServlet extends AbstractPwmServlet
     {
     {
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
 
 
-        final ConfigManagerCertificateAction action = readProcessAction( pwmRequest );
+        final Optional<ConfigManagerCertificateAction> action = readProcessAction( pwmRequest );
         final ArrayList<CertificateDebugDataItem> certificateDebugDataItems = new ArrayList<>( makeCertificateDebugData( pwmRequest.getDomainConfig() ) );
         final ArrayList<CertificateDebugDataItem> certificateDebugDataItems = new ArrayList<>( makeCertificateDebugData( pwmRequest.getDomainConfig() ) );
 
 
-        if ( action == ConfigManagerCertificateAction.certificateData )
+        if ( action.isPresent() && action.get() == ConfigManagerCertificateAction.certificateData )
         {
         {
             final RestResultBean restResultBean = RestResultBean.withData( certificateDebugDataItems );
             final RestResultBean restResultBean = RestResultBean.withData( certificateDebugDataItems );
             pwmRequest.outputJsonResult( restResultBean );
             pwmRequest.outputJsonResult( restResultBean );

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

@@ -92,17 +92,10 @@ public class ConfigManagerLocalDBServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ConfigManagerAction readProcessAction( final PwmRequest request )
+    protected Optional<ConfigManagerAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ConfigManagerAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     @Override
     @Override
@@ -111,10 +104,10 @@ public class ConfigManagerLocalDBServlet extends AbstractPwmServlet
     {
     {
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
 
 
-        final ConfigManagerAction processAction = readProcessAction( pwmRequest );
-        if ( processAction != null )
+        final Optional<ConfigManagerAction> processAction = readProcessAction( pwmRequest );
+        if ( processAction.isPresent() )
         {
         {
-            switch ( processAction )
+            switch ( processAction.get() )
             {
             {
                 case exportLocalDB:
                 case exportLocalDB:
                     doExportLocalDB( pwmRequest );
                     doExportLocalDB( pwmRequest );
@@ -125,7 +118,7 @@ public class ConfigManagerLocalDBServlet extends AbstractPwmServlet
                     return;
                     return;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( processAction );
+                    JavaHelper.unhandledSwitchStatement( processAction.get() );
 
 
 
 
             }
             }

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

@@ -105,17 +105,17 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
     {
     {
         checkPersistentLoginCookie( pwmRequest );
         checkPersistentLoginCookie( pwmRequest );
 
 
-        final ConfigManagerLoginAction processAction = readProcessAction( pwmRequest );
-        if ( processAction != null )
+        final Optional<ConfigManagerLoginAction> processAction = readProcessAction( pwmRequest );
+        if ( processAction.isPresent() )
         {
         {
-            switch ( processAction )
+            switch ( processAction.get() )
             {
             {
                 case login:
                 case login:
                     processLoginRequest( pwmRequest );
                     processLoginRequest( pwmRequest );
                     break;
                     break;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( processAction );
+                    JavaHelper.unhandledSwitchStatement( processAction.get() );
 
 
             }
             }
             return;
             return;
@@ -163,17 +163,10 @@ public class ConfigManagerLoginServlet extends AbstractPwmServlet
 
 
 
 
     @Override
     @Override
-    protected ConfigManagerLoginAction readProcessAction( final PwmRequest request )
+    protected Optional<ConfigManagerLoginAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ConfigManagerLoginAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ConfigManagerLoginAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
 
 

+ 8 - 14
server/src/main/java/password/pwm/http/servlet/configmanager/ConfigManagerServlet.java

@@ -25,8 +25,8 @@ import org.apache.commons.csv.CSVPrinter;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.IOUtils;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.Permission;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.config.AppConfig;
 import password.pwm.config.AppConfig;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.ConfigurationReader;
@@ -76,6 +76,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 import java.util.zip.ZipOutputStream;
 import java.util.zip.ZipOutputStream;
 
 
@@ -117,17 +118,10 @@ public class ConfigManagerServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ConfigManagerAction readProcessAction( final PwmRequest request )
+    protected Optional<ConfigManagerAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ConfigManagerAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     public static void verifyConfigAccess( final PwmRequest pwmRequest )
     public static void verifyConfigAccess( final PwmRequest pwmRequest )
@@ -148,10 +142,10 @@ public class ConfigManagerServlet extends AbstractPwmServlet
     {
     {
         verifyConfigAccess( pwmRequest );
         verifyConfigAccess( pwmRequest );
 
 
-        final ConfigManagerAction processAction = readProcessAction( pwmRequest );
-        if ( processAction != null )
+        final Optional<ConfigManagerAction> processAction = readProcessAction( pwmRequest );
+        if ( processAction.isPresent() )
         {
         {
-            switch ( processAction )
+            switch ( processAction.get() )
             {
             {
                 case lockConfiguration:
                 case lockConfiguration:
                     restLockConfiguration( pwmRequest );
                     restLockConfiguration( pwmRequest );
@@ -186,7 +180,7 @@ public class ConfigManagerServlet extends AbstractPwmServlet
                     return;
                     return;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( processAction );
+                    JavaHelper.unhandledSwitchStatement( processAction.get() );
             }
             }
             return;
             return;
         }
         }

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

@@ -89,17 +89,10 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
     }
     }
 
 
     @Override
     @Override
-    protected ConfigManagerAction readProcessAction( final PwmRequest request )
+    protected Optional<ConfigManagerAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        try
-        {
-            return ConfigManagerAction.valueOf( request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
-        }
-        catch ( final IllegalArgumentException e )
-        {
-            return null;
-        }
+        return JavaHelper.readEnumFromString( ConfigManagerAction.class, request.readParameterAsString( PwmConstants.PARAM_ACTION_REQUEST ) );
     }
     }
 
 
     @Override
     @Override
@@ -108,10 +101,10 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
     {
     {
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
         ConfigManagerServlet.verifyConfigAccess( pwmRequest );
 
 
-        final ConfigManagerAction processAction = readProcessAction( pwmRequest );
-        if ( processAction != null )
+        final Optional<ConfigManagerAction> processAction = readProcessAction( pwmRequest );
+        if ( processAction.isPresent() )
         {
         {
-            switch ( processAction )
+            switch ( processAction.get() )
             {
             {
                 case uploadWordlist:
                 case uploadWordlist:
                     restUploadWordlist( pwmRequest );
                     restUploadWordlist( pwmRequest );
@@ -126,7 +119,7 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
                     return;
                     return;
 
 
                 default:
                 default:
-                    JavaHelper.unhandledSwitchStatement( processAction );
+                    JavaHelper.unhandledSwitchStatement( processAction.get() );
             }
             }
             return;
             return;
         }
         }
@@ -139,12 +132,11 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
 
 
     {
     {
         final HttpServletRequest req = pwmRequest.getHttpServletRequest();
         final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-        final String wordlistTypeParam = pwmRequest.readParameterAsString( "wordlist" );
-        final WordlistType wordlistType = WordlistType.valueOf( wordlistTypeParam );
+        final Optional<WordlistType> wordlistType = JavaHelper.readEnumFromString( WordlistType.class, pwmRequest.readParameterAsString( "wordlist" ) );
 
 
-        if ( wordlistType == null )
+        if ( wordlistType.isEmpty() )
         {
         {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "unknown wordlist type: " + wordlistTypeParam );
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "unknown wordlist type" );
             pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) );
             pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) );
             LOGGER.error( pwmRequest, () -> "error during import: " + errorInformation.toDebugStr() );
             LOGGER.error( pwmRequest, () -> "error during import: " + errorInformation.toDebugStr() );
             return;
             return;
@@ -164,7 +156,7 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
         {
         {
             try ( InputStream inputStream = optionalInputStream.get() )
             try ( InputStream inputStream = optionalInputStream.get() )
             {
             {
-                wordlistType.forType( pwmRequest.getPwmApplication() ).populate( inputStream );
+                wordlistType.get().forType( pwmRequest.getPwmApplication() ).populate( inputStream );
             }
             }
             catch ( final PwmUnrecoverableException e )
             catch ( final PwmUnrecoverableException e )
             {
             {
@@ -182,12 +174,11 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
     void restClearWordlist( final PwmRequest pwmRequest )
     void restClearWordlist( final PwmRequest pwmRequest )
             throws IOException, PwmUnrecoverableException
             throws IOException, PwmUnrecoverableException
     {
     {
-        final String wordlistTypeParam = pwmRequest.readParameterAsString( "wordlist" );
-        final WordlistType wordlistType = WordlistType.valueOf( wordlistTypeParam );
+        final Optional<WordlistType> wordlistType = JavaHelper.readEnumFromString( WordlistType.class, pwmRequest.readParameterAsString( "wordlist" ) );
 
 
-        if ( wordlistType == null )
+        if ( wordlistType.isEmpty() )
         {
         {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "unknown wordlist type: " + wordlistTypeParam );
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "unknown wordlist type" );
             pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) );
             pwmRequest.outputJsonResult( RestResultBean.fromError( errorInformation, pwmRequest ) );
             LOGGER.error( pwmRequest, () -> "error during clear: " + errorInformation.toDebugStr() );
             LOGGER.error( pwmRequest, () -> "error during clear: " + errorInformation.toDebugStr() );
             return;
             return;
@@ -195,7 +186,7 @@ public class ConfigManagerWordlistServlet extends AbstractPwmServlet
 
 
         try
         try
         {
         {
-            wordlistType.forType( pwmRequest.getPwmApplication() ).clear();
+            wordlistType.get().forType( pwmRequest.getPwmApplication() ).clear();
         }
         }
         catch ( final Exception e )
         catch ( final Exception e )
         {
         {

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

@@ -204,10 +204,10 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
 
         checkForLocaleSwitch( pwmRequest, forgottenPasswordBean );
         checkForLocaleSwitch( pwmRequest, forgottenPasswordBean );
 
 
-        final ProcessAction action = this.readProcessAction( pwmRequest );
+        final Optional<? extends ProcessAction> action = this.readProcessAction( pwmRequest );
 
 
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
-        if ( action == null )
+        if ( action.isEmpty() )
         {
         {
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.ForgottenPassword, ForgottenPasswordAction.enterCode ) )
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.ForgottenPassword, ForgottenPasswordAction.enterCode ) )
             {
             {
@@ -243,14 +243,15 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             forgottenPasswordBean.getProgress().clearTokenSentStatus();
             forgottenPasswordBean.getProgress().clearTokenSentStatus();
         }
         }
 
 
+        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "actionChoice without userInfo present" ) );
 
 
         final boolean disallowAllButUnlock;
         final boolean disallowAllButUnlock;
         {
         {
-            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
+
             final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
             final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
                     PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS,
                     PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS,
-                    RecoveryMinLifetimeOption.class
-            );
+                    RecoveryMinLifetimeOption.class );
             disallowAllButUnlock = minLifetimeOption == RecoveryMinLifetimeOption.UNLOCKONLY
             disallowAllButUnlock = minLifetimeOption == RecoveryMinLifetimeOption.UNLOCKONLY
                     && userInfo.isPasswordLocked();
                     && userInfo.isPasswordLocked();
         }
         }
@@ -272,7 +273,6 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                     case resetPassword:
                     case resetPassword:
                         if ( disallowAllButUnlock )
                         if ( disallowAllButUnlock )
                         {
                         {
-                            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
                             PasswordUtility.throwPasswordTooSoonException( userInfo, pwmRequest.getLabel() );
                             PasswordUtility.throwPasswordTooSoonException( userInfo, pwmRequest.getLabel() );
                         }
                         }
                         this.executeResetPassword( pwmRequest );
                         this.executeResetPassword( pwmRequest );
@@ -607,7 +607,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         final String userEnteredCode = pwmRequest.readParameterAsString( PwmConstants.PARAM_TOKEN );
         final String userEnteredCode = pwmRequest.readParameterAsString( PwmConstants.PARAM_TOKEN );
         LOGGER.debug( pwmRequest, () -> String.format( "entered OTP: %s", userEnteredCode ) );
         LOGGER.debug( pwmRequest, () -> String.format( "entered OTP: %s", userEnteredCode ) );
 
 
-        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
+        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "enterOtp without userInfo present" ) );
         final OTPUserRecord otpUserRecord = userInfo.getOtpUserRecord();
         final OTPUserRecord otpUserRecord = userInfo.getOtpUserRecord();
 
 
         final boolean otpPassed;
         final boolean otpPassed;
@@ -780,7 +781,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         catch ( final ChaiValidationException e )
         catch ( final ChaiValidationException e )
         {
         {
             LOGGER.debug( pwmRequest, () -> "chai validation error checking user responses: " + e.getMessage() );
             LOGGER.debug( pwmRequest, () -> "chai validation error checking user responses: " + e.getMessage() );
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.forChaiError( e.getErrorCode() ) );
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
             handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, errorInformation );
             handleUserVerificationBadAttempt( pwmRequest, forgottenPasswordBean, errorInformation );
             return ProcessStatus.Continue;
             return ProcessStatus.Continue;
         }
         }
@@ -822,7 +823,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         }
         }
 
 
         {
         {
-            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
+            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "resendToken without userInfo present" ) );
             final TokenDestinationItem tokenDestinationItem = forgottenPasswordBean.getProgress().getTokenDestination();
             final TokenDestinationItem tokenDestinationItem = forgottenPasswordBean.getProgress().getTokenDestination();
             ForgottenPasswordUtil.initializeAndSendToken( pwmRequest.getPwmRequestContext(), userInfo, tokenDestinationItem );
             ForgottenPasswordUtil.initializeAndSendToken( pwmRequest.getPwmRequestContext(), userInfo, tokenDestinationItem );
         }
         }
@@ -897,7 +899,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                     }
                     }
                     else
                     else
                     {
                     {
-                        throw new PwmDataValidationException( new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE, "incorrect value for '" + attrName + "'", new String[]
+                        throw new PwmDataValidationException( new ErrorInformation(
+                                PwmError.ERROR_INCORRECT_RESPONSE, "incorrect value for '" + attrName + "'", new String[]
                                 {
                                 {
                                         formConfiguration.getLabel( pwmRequest.getLocale() ),
                                         formConfiguration.getLabel( pwmRequest.getLocale() ),
                                 }
                                 }
@@ -907,7 +910,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                 catch ( final ChaiOperationException e )
                 catch ( final ChaiOperationException e )
                 {
                 {
                     LOGGER.error( pwmRequest, () -> "error during param validation of '" + attrName + "', error: " + e.getMessage() );
                     LOGGER.error( pwmRequest, () -> "error during param validation of '" + attrName + "', error: " + e.getMessage() );
-                    throw new PwmDataValidationException( new ErrorInformation( PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]
+                    throw new PwmDataValidationException( new ErrorInformation(
+                            PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]
                             {
                             {
                                     formConfiguration.getLabel( pwmRequest.getLocale() ),
                                     formConfiguration.getLabel( pwmRequest.getLocale() ),
                             }
                             }
@@ -1100,11 +1104,8 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_SUCCESSES );
             StatisticsClient.incrementStat( pwmRequest, Statistic.RECOVERY_SUCCESSES );
         }
         }
 
 
-        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
-        if ( userInfo == null )
-        {
-            throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller" );
-        }
+        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "controller execution without userInfo present" ) );
 
 
         // check if user's pw is within min lifetime window
         // check if user's pw is within min lifetime window
         final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
         final RecoveryMinLifetimeOption minLifetimeOption = forgottenPasswordProfile.readSettingAsEnum(
@@ -1174,8 +1175,9 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             theUser.unlockPassword();
             theUser.unlockPassword();
 
 
             // mark the event log
             // mark the event log
-            final UserInfo userInfoBean = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
-            AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.UNLOCK_PASSWORD, userInfoBean );
+            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "executeUnlock without userInfo present" ) );
+            AuditServiceClient.submitUserEvent( pwmRequest, AuditEvent.UNLOCK_PASSWORD, userInfo );
 
 
             ForgottenPasswordUtil.sendUnlockNoticeEmail( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
             ForgottenPasswordUtil.sendUnlockNoticeEmail( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
 
 
@@ -1185,7 +1187,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
         {
         {
             final String errorMsg = "unable to unlock user " + userIdentity + " error: " + e.getMessage();
             final String errorMsg = "unable to unlock user " + userIdentity + " error: " + e.getMessage();
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg );
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_UNLOCK_FAILURE, errorMsg );
-            LOGGER.error( pwmRequest, () -> errorInformation.toDebugStr() );
+            LOGGER.error( pwmRequest, errorInformation::toDebugStr );
             pwmRequest.respondWithError( errorInformation, true );
             pwmRequest.respondWithError( errorInformation, true );
         }
         }
         finally
         finally
@@ -1287,7 +1289,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
     }
     }
 
 
     private void checkForLocaleSwitch( final PwmRequest pwmRequest, final ForgottenPasswordBean forgottenPasswordBean )
     private void checkForLocaleSwitch( final PwmRequest pwmRequest, final ForgottenPasswordBean forgottenPasswordBean )
-            throws PwmUnrecoverableException, IOException, ServletException
+            throws PwmUnrecoverableException
     {
     {
         if ( forgottenPasswordBean.getUserIdentity() == null || forgottenPasswordBean.getUserLocale() == null )
         if ( forgottenPasswordBean.getUserIdentity() == null || forgottenPasswordBean.getUserLocale() == null )
         {
         {
@@ -1319,7 +1321,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
                     PwmError.ERROR_INTERNAL,
                     PwmError.ERROR_INTERNAL,
                     "unexpected error while re-loading user data due to locale change: " + e.getErrorInformation().toDebugStr()
                     "unexpected error while re-loading user data due to locale change: " + e.getErrorInformation().toDebugStr()
             );
             );
-            LOGGER.error( pwmRequest, () -> errorInformation.toDebugStr() );
+            LOGGER.error( pwmRequest, errorInformation::toDebugStr );
             setLastError( pwmRequest, errorInformation );
             setLastError( pwmRequest, errorInformation );
         }
         }
     }
     }
@@ -1359,9 +1361,11 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
 
             case OTP:
             case OTP:
             {
             {
+                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                        .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "dispatch otp method without userInfo present" ) );
                 pwmRequest.setAttribute(
                 pwmRequest.setAttribute(
                         PwmRequestAttribute.ForgottenPasswordOtpRecord,
                         PwmRequestAttribute.ForgottenPasswordOtpRecord,
-                        ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean ).getOtpUserRecord()
+                        userInfo.getOtpUserRecord()
                 );
                 );
                 pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_ENTER_OTP );
                 pwmRequest.forwardToJsp( JspUrl.RECOVER_PASSWORD_ENTER_OTP );
             }
             }
@@ -1391,7 +1395,9 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
 
                 if ( !progress.isTokenSent() )
                 if ( !progress.isTokenSent() )
                 {
                 {
-                    final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
+                    final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                            .orElseThrow( () -> PwmUnrecoverableException.newException(
+                                    PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "dispatch token method without userInfo present" ) );
                     ForgottenPasswordUtil.initializeAndSendToken( pwmRequest.getPwmRequestContext(), userInfo, progress.getTokenDestination() );
                     ForgottenPasswordUtil.initializeAndSendToken( pwmRequest.getPwmRequestContext(), userInfo, progress.getTokenDestination() );
                     progress.setTokenSent( true );
                     progress.setTokenSent( true );
                 }
                 }
@@ -1406,7 +1412,9 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
 
             case REMOTE_RESPONSES:
             case REMOTE_RESPONSES:
             {
             {
-                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean );
+                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequest.getPwmRequestContext(), forgottenPasswordBean )
+                        .orElseThrow( () -> PwmUnrecoverableException.newException(
+                                PwmError.ERROR_RECOVERY_SEQUENCE_INCOMPLETE, "dispatch remote responses method without userInfo present" ) );
                 final VerificationMethodSystem remoteMethod;
                 final VerificationMethodSystem remoteMethod;
                 if ( forgottenPasswordBean.getProgress().getRemoteRecoveryMethod() == null )
                 if ( forgottenPasswordBean.getProgress().getRemoteRecoveryMethod() == null )
                 {
                 {
@@ -1445,7 +1453,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
 
 
 
 
             default:
             default:
-                throw new UnsupportedOperationException( "unexpected method during forward: " + method.toString() );
+                throw new UnsupportedOperationException( "unexpected method during forward: " + method );
         }
         }
 
 
     }
     }

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

@@ -327,11 +327,9 @@ class ForgottenPasswordStageProcessor
                 StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_SUCCESSES );
                 StatisticsClient.incrementStat( pwmRequestContext.getPwmApplication(), Statistic.RECOVERY_SUCCESSES );
             }
             }
 
 
-            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );
-            if ( userInfo == null )
-            {
-                throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller" );
-            }
+            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException(
+                            PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller 6" ) );
 
 
             // check if user's pw is within min lifetime window
             // check if user's pw is within min lifetime window
             final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
             final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
@@ -367,7 +365,9 @@ class ForgottenPasswordStageProcessor
             final SessionLabel sessionLabel = pwmRequestContext.getSessionLabel();
             final SessionLabel sessionLabel = pwmRequestContext.getSessionLabel();
             final DomainConfig config = pwmDomain.getConfig();
             final DomainConfig config = pwmDomain.getConfig();
 
 
-            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );
+            final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException(
+                            PwmError.ERROR_INTERNAL, "unable to load userInfo while processing forgotten password controller 7" ) );
 
 
             final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
             final ForgottenPasswordProfile forgottenPasswordProfile = ForgottenPasswordUtil.forgottenPasswordProfile( pwmDomain, forgottenPasswordBean );
 
 

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

@@ -265,7 +265,7 @@ public class ForgottenPasswordStateMachine
 
 
                     if ( !passwordCheckInfo.isPassed() )
                     if ( !passwordCheckInfo.isPassed() )
                     {
                     {
-                        final PwmError pwmError = PwmError.forErrorNumber( passwordCheckInfo.getErrorCode() );
+                        final PwmError pwmError = PwmError.forErrorNumber( passwordCheckInfo.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
                         throw PwmUnrecoverableException.newException( pwmError, passwordCheckInfo.getMessage() );
                         throw PwmUnrecoverableException.newException( pwmError, passwordCheckInfo.getMessage() );
                     }
                     }
                 }
                 }
@@ -375,7 +375,10 @@ public class ForgottenPasswordStateMachine
 
 
                 final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo(
                 final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo(
                         forgottenPasswordStateMachine.getRequestContext(),
                         forgottenPasswordStateMachine.getRequestContext(),
-                        forgottenPasswordStateMachine.getForgottenPasswordBean() );
+                        forgottenPasswordStateMachine.getForgottenPasswordBean() )
+                        .orElseThrow( () -> PwmUnrecoverableException.newException(
+                                PwmError.ERROR_INTERNAL, "unable to load userInfo while processing TokenChoiceStageHandler.applyForm" ) );
+
                 ForgottenPasswordUtil.initializeAndSendToken( forgottenPasswordStateMachine.getRequestContext(), userInfo, selectedItem.get() );
                 ForgottenPasswordUtil.initializeAndSendToken( forgottenPasswordStateMachine.getRequestContext(), userInfo, selectedItem.get() );
                 forgottenPasswordStateMachine.getForgottenPasswordBean().getProgress().setTokenSent( true );
                 forgottenPasswordStateMachine.getForgottenPasswordBean().getProgress().setTokenSent( true );
             }
             }
@@ -467,7 +470,9 @@ public class ForgottenPasswordStateMachine
                 final PwmRequestContext pwmRequestContext = forgottenPasswordStateMachine.getRequestContext();
                 final PwmRequestContext pwmRequestContext = forgottenPasswordStateMachine.getRequestContext();
                 final String userEnteredCode = formValues.get( PwmConstants.PARAM_OTP_TOKEN );
                 final String userEnteredCode = formValues.get( PwmConstants.PARAM_OTP_TOKEN );
 
 
-                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordStateMachine.getForgottenPasswordBean() );
+                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordStateMachine.getForgottenPasswordBean() )
+                        .orElseThrow( () -> PwmUnrecoverableException.newException(
+                                PwmError.ERROR_INTERNAL, "unable to load userInfo while processing OTPVerificationHandler.applyForm" ) );
                 final OTPUserRecord otpUserRecord = userInfo.getOtpUserRecord();
                 final OTPUserRecord otpUserRecord = userInfo.getOtpUserRecord();
 
 
                 ErrorInformation errorInformation = null;
                 ErrorInformation errorInformation = null;
@@ -521,7 +526,9 @@ public class ForgottenPasswordStateMachine
 
 
                 final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo(
                 final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo(
                         forgottenPasswordStateMachine.getRequestContext(),
                         forgottenPasswordStateMachine.getRequestContext(),
-                        forgottenPasswordStateMachine.getForgottenPasswordBean() );
+                        forgottenPasswordStateMachine.getForgottenPasswordBean() )
+                        .orElseThrow( () -> PwmUnrecoverableException.newException(
+                                PwmError.ERROR_INTERNAL, "unable to load userInfo while processing OTPVerificationHandler.generateForm" ) );
 
 
                 final OTPUserRecord otpUserRecord = userInfo == null ? null : userInfo.getOtpUserRecord();
                 final OTPUserRecord otpUserRecord = userInfo == null ? null : userInfo.getOtpUserRecord();
 
 
@@ -541,8 +548,8 @@ public class ForgottenPasswordStateMachine
                             Display.Display_RecoverOTPIdentified,
                             Display.Display_RecoverOTPIdentified,
                             pwmRequestContext.getDomainConfig(),
                             pwmRequestContext.getDomainConfig(),
                             new String[]
                             new String[]
-                            {
-                                    identifier,
+                                    {
+                                            identifier,
                                     }
                                     }
                     );
                     );
                 }
                 }
@@ -634,7 +641,7 @@ public class ForgottenPasswordStateMachine
                         new String[]
                         new String[]
                                 {
                                 {
                                         tokenDisplay,
                                         tokenDisplay,
-                                        }
+                                }
                 );
                 );
 
 
                 final PresentableFormRow formRow = PresentableFormRow.builder()
                 final PresentableFormRow formRow = PresentableFormRow.builder()
@@ -753,7 +760,7 @@ public class ForgottenPasswordStateMachine
                             "incorrect value for attribute '" + formConfiguration.getName() + "'", new String[]
                             "incorrect value for attribute '" + formConfiguration.getName() + "'", new String[]
                             {
                             {
                                     formConfiguration.getLabel( locale ),
                                     formConfiguration.getLabel( locale ),
-                                    }
+                            }
                     );
                     );
 
 
                     throw new PwmUnrecoverableException( errorInformation );
                     throw new PwmUnrecoverableException( errorInformation );
@@ -797,7 +804,7 @@ public class ForgottenPasswordStateMachine
                                         "incorrect value for '" + attrName + "'", new String[]
                                         "incorrect value for '" + attrName + "'", new String[]
                                         {
                                         {
                                                 formConfiguration.getLabel( locale ),
                                                 formConfiguration.getLabel( locale ),
-                                                }
+                                        }
                                 ) );
                                 ) );
                             }
                             }
                         }
                         }
@@ -808,7 +815,7 @@ public class ForgottenPasswordStateMachine
                                     PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]
                                     PwmError.ERROR_INCORRECT_RESPONSE, "ldap error testing value for '" + attrName + "'", new String[]
                                     {
                                     {
                                             formConfiguration.getLabel( locale ),
                                             formConfiguration.getLabel( locale ),
-                                            }
+                                    }
                             ) );
                             ) );
                         }
                         }
                         catch ( final ChaiUnavailableException e )
                         catch ( final ChaiUnavailableException e )

+ 14 - 22
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -133,7 +133,7 @@ public class ForgottenPasswordUtil
         return Collections.unmodifiableSet( result );
         return Collections.unmodifiableSet( result );
     }
     }
 
 
-    static UserInfo readUserInfo(
+    static Optional<UserInfo> readUserInfo(
             final PwmRequestContext pwmRequestContext,
             final PwmRequestContext pwmRequestContext,
             final ForgottenPasswordBean forgottenPasswordBean
             final ForgottenPasswordBean forgottenPasswordBean
     )
     )
@@ -141,17 +141,16 @@ public class ForgottenPasswordUtil
     {
     {
         if ( forgottenPasswordBean.getUserIdentity() == null )
         if ( forgottenPasswordBean.getUserIdentity() == null )
         {
         {
-            return null;
+            return Optional.empty();
         }
         }
 
 
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
         final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
 
 
-        return UserInfoFactory.newUserInfoUsingProxy(
+        return Optional.of( UserInfoFactory.newUserInfoUsingProxy(
                 pwmRequestContext.getPwmApplication(),
                 pwmRequestContext.getPwmApplication(),
                 pwmRequestContext.getSessionLabel(),
                 pwmRequestContext.getSessionLabel(),
                 userIdentity,
                 userIdentity,
-                pwmRequestContext.getLocale()
-        );
+                pwmRequestContext.getLocale() ) );
     }
     }
 
 
     static Optional<ResponseSet> readResponseSet(
     static Optional<ResponseSet> readResponseSet(
@@ -205,7 +204,7 @@ public class ForgottenPasswordUtil
             return;
             return;
         }
         }
 
 
-        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean );
+        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
         final MacroRequest macroRequest = MacroRequest.forUser(
         final MacroRequest macroRequest = MacroRequest.forUser(
                 pwmRequestContext.getPwmApplication(),
                 pwmRequestContext.getPwmApplication(),
                 pwmRequestContext.getSessionLabel(),
                 pwmRequestContext.getSessionLabel(),
@@ -263,17 +262,15 @@ public class ForgottenPasswordUtil
         final String profileID = forgottenPasswordBean.getForgottenPasswordProfileID();
         final String profileID = forgottenPasswordBean.getForgottenPasswordProfileID();
         final ForgottenPasswordProfile forgottenPasswordProfile = pwmRequestContext.getDomainConfig().getForgottenPasswordProfiles().get( profileID );
         final ForgottenPasswordProfile forgottenPasswordProfile = pwmRequestContext.getDomainConfig().getForgottenPasswordProfiles().get( profileID );
         final MessageSendMethod tokenSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class );
         final MessageSendMethod tokenSendMethod = forgottenPasswordProfile.readSettingAsEnum( PwmSetting.RECOVERY_TOKEN_SEND_METHOD, MessageSendMethod.class );
-        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );
+        final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
 
 
-        final List<TokenDestinationItem> items = TokenUtil.figureAvailableTokenDestinations(
+        return TokenUtil.figureAvailableTokenDestinations(
                 pwmRequestContext.getPwmDomain(),
                 pwmRequestContext.getPwmDomain(),
                 pwmRequestContext.getSessionLabel(),
                 pwmRequestContext.getSessionLabel(),
                 pwmRequestContext.getLocale(),
                 pwmRequestContext.getLocale(),
                 userInfo,
                 userInfo,
                 tokenSendMethod
                 tokenSendMethod
         );
         );
-
-        return Collections.unmodifiableList( items );
     }
     }
 
 
     static void verifyRequirementsForAuthMethod(
     static void verifyRequirementsForAuthMethod(
@@ -305,7 +302,7 @@ public class ForgottenPasswordUtil
 
 
             case OTP:
             case OTP:
             {
             {
-                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );
+                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
                 if ( userInfo.getOtpUserRecord() == null )
                 if ( userInfo.getOtpUserRecord() == null )
                 {
                 {
                     final String errorMsg = "could not find a one time password configuration for " + userInfo.getUserIdentity();
                     final String errorMsg = "could not find a one time password configuration for " + userInfo.getUserIdentity();
@@ -317,7 +314,7 @@ public class ForgottenPasswordUtil
 
 
             case CHALLENGE_RESPONSES:
             case CHALLENGE_RESPONSES:
             {
             {
-                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean );
+                final UserInfo userInfo = ForgottenPasswordUtil.readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
                 final Optional<ResponseSet> responseSet = ForgottenPasswordUtil.readResponseSet( pwmRequestContext, forgottenPasswordBean );
                 final Optional<ResponseSet> responseSet = ForgottenPasswordUtil.readResponseSet( pwmRequestContext, forgottenPasswordBean );
                 if ( responseSet.isEmpty() )
                 if ( responseSet.isEmpty() )
                 {
                 {
@@ -679,7 +676,7 @@ public class ForgottenPasswordUtil
 
 
         forgottenPasswordBean.setUserIdentity( userIdentity );
         forgottenPasswordBean.setUserIdentity( userIdentity );
 
 
-        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean );
+        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
 
 
         final ForgottenPasswordProfile forgottenPasswordProfile = forgottenPasswordProfile(
         final ForgottenPasswordProfile forgottenPasswordProfile = forgottenPasswordProfile(
                 pwmDomain,
                 pwmDomain,
@@ -717,7 +714,7 @@ public class ForgottenPasswordUtil
             }
             }
             catch ( final ChaiUnavailableException e )
             catch ( final ChaiUnavailableException e )
             {
             {
-                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
             }
             }
         }
         }
         else
         else
@@ -745,7 +742,7 @@ public class ForgottenPasswordUtil
             }
             }
             catch ( final ChaiUnavailableException e )
             catch ( final ChaiUnavailableException e )
             {
             {
-                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
             }
             }
         }
         }
 
 
@@ -778,7 +775,7 @@ public class ForgottenPasswordUtil
             return requiredAttributesForm;
             return requiredAttributesForm;
         }
         }
 
 
-        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean );
+        final UserInfo userInfo = readUserInfo( pwmRequestContext, forgottenPasswordBean ).orElseThrow();
         final List<FormConfiguration> returnList = new ArrayList<>();
         final List<FormConfiguration> returnList = new ArrayList<>();
         for ( final FormConfiguration formItem : requiredAttributesForm )
         for ( final FormConfiguration formItem : requiredAttributesForm )
         {
         {
@@ -866,12 +863,7 @@ public class ForgottenPasswordUtil
             final Set<IdentityVerificationMethod> otherOptionalMethodChoices = CollectionUtil.copiedEnumSet( remainingAvailableOptionalMethods, IdentityVerificationMethod.class );
             final Set<IdentityVerificationMethod> otherOptionalMethodChoices = CollectionUtil.copiedEnumSet( remainingAvailableOptionalMethods, IdentityVerificationMethod.class );
             otherOptionalMethodChoices.remove( thisMethod );
             otherOptionalMethodChoices.remove( thisMethod );
 
 
-            if ( !otherOptionalMethodChoices.isEmpty() )
-            {
-                return true;
-            }
+            return !otherOptionalMethodChoices.isEmpty();
         }
         }
-
-        return false;
     }
     }
 }
 }

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

@@ -636,14 +636,14 @@ public class HelpdeskServlet extends ControlledPwmServlet
         catch ( final ChaiPasswordPolicyException e )
         catch ( final ChaiPasswordPolicyException e )
         {
         {
             final ChaiError passwordError = e.getErrorCode();
             final ChaiError passwordError = e.getErrorCode();
-            final PwmError pwmError = PwmError.forChaiError( passwordError );
-            pwmRequest.respondWithError( new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError ) );
-            LOGGER.trace( pwmRequest, () -> "ChaiPasswordPolicyException was thrown while resetting password: " + e.toString() );
+            final PwmError pwmError = PwmError.forChaiError( passwordError ).orElse( PwmError.PASSWORD_UNKNOWN_VALIDATION );
+            pwmRequest.respondWithError( new ErrorInformation( pwmError ) );
+            LOGGER.trace( pwmRequest, () -> "ChaiPasswordPolicyException was thrown while resetting password: " + e.getMessage() );
             return ProcessStatus.Halt;
             return ProcessStatus.Halt;
         }
         }
         catch ( final ChaiOperationException e )
         catch ( final ChaiOperationException e )
         {
         {
-            final PwmError returnMsg = PwmError.forChaiError( e.getErrorCode() ) == null ? PwmError.ERROR_INTERNAL : PwmError.forChaiError( e.getErrorCode() );
+            final PwmError returnMsg = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
             final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() );
             final ErrorInformation error = new ErrorInformation( returnMsg, e.getMessage() );
             pwmRequest.respondWithError( error );
             pwmRequest.respondWithError( error );
             LOGGER.warn( pwmRequest, () -> "error resetting password for user '" + userIdentity.toDisplayString() + "'' " + error.toDebugStr() + ", " + e.getMessage() );
             LOGGER.warn( pwmRequest, () -> "error resetting password for user '" + userIdentity.toDisplayString() + "'' " + error.toDebugStr() + ", " + e.getMessage() );

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

@@ -81,6 +81,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 /**
 /**
@@ -171,17 +172,17 @@ public class NewUserServlet extends ControlledPwmServlet
             return ProcessStatus.Halt;
             return ProcessStatus.Halt;
         }
         }
 
 
-        final ProcessAction action = this.readProcessAction( pwmRequest );
+        final Optional<? extends ProcessAction> action = this.readProcessAction( pwmRequest );
 
 
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
         // convert a url command like /public/newuser/12321321 to redirect with a process action.
-        if ( action == null )
+        if ( action.isEmpty() )
         {
         {
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.NewUser, NewUserAction.enterCode ) )
             if ( pwmRequest.convertURLtokenCommand( PwmServletDefinition.NewUser, NewUserAction.enterCode ) )
             {
             {
                 return ProcessStatus.Halt;
                 return ProcessStatus.Halt;
             }
             }
         }
         }
-        else if ( action != NewUserAction.complete && action != NewUserAction.checkProgress )
+        else if ( action.get() != NewUserAction.complete && action.get() != NewUserAction.checkProgress )
         {
         {
             if ( pwmRequest.isAuthenticated() )
             if ( pwmRequest.isAuthenticated() )
             {
             {

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

@@ -113,7 +113,8 @@ class NewUserUtils
     {
     {
         if ( !passwordCheckInfo.isPassed() )
         if ( !passwordCheckInfo.isPassed() )
         {
         {
-            final ErrorInformation errorInformation = PwmError.forErrorNumber( passwordCheckInfo.getErrorCode() ).toInfo();
+            final ErrorInformation errorInformation = PwmError.forErrorNumber( passwordCheckInfo.getErrorCode() )
+                    .orElse( PwmError.ERROR_INTERNAL ).toInfo();
             throw new PwmOperationalException( errorInformation );
             throw new PwmOperationalException( errorInformation );
         }
         }
         if ( passwordCheckInfo.getMatch() != PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH )
         if ( passwordCheckInfo.getMatch() != PasswordUtility.PasswordCheckInfo.MatchStatus.MATCH )

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

@@ -64,10 +64,10 @@ public class OAuthConsumerServlet extends AbstractPwmServlet
 
 
 
 
     @Override
     @Override
-    protected ProcessAction readProcessAction( final PwmRequest request )
+    protected Optional<? extends ProcessAction> readProcessAction( final PwmRequest request )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        return null;
+        return Optional.empty();
     }
     }
 
 
     @Override
     @Override

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

@@ -79,7 +79,7 @@ public class PwmScriptTag extends BodyTagSupport
     {
     {
         if ( input == null )
         if ( input == null )
         {
         {
-            return null;
+            return "";
         }
         }
 
 
         final Matcher matcher = SCRIPT_TAG_PATTERN.matcher( input );
         final Matcher matcher = SCRIPT_TAG_PATTERN.matcher( input );

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

@@ -186,10 +186,10 @@ public class LdapOperationsHelper
             }
             }
             final StringBuilder errorMsg = new StringBuilder();
             final StringBuilder errorMsg = new StringBuilder();
             errorMsg.append( "error connecting as proxy user: " );
             errorMsg.append( "error connecting as proxy user: " );
-            final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() );
-            if ( pwmError != null && pwmError != PwmError.ERROR_INTERNAL )
+            final Optional<PwmError> pwmError = PwmError.forChaiError( e.getErrorCode() );
+            if ( pwmError.isPresent() && pwmError.get() != PwmError.ERROR_INTERNAL )
             {
             {
-                errorMsg.append( new ErrorInformation( pwmError, e.getMessage() ).toDebugStr() );
+                errorMsg.append( new ErrorInformation( pwmError.get(), e.getMessage() ).toDebugStr() );
             }
             }
             else
             else
             {
             {

+ 13 - 18
server/src/main/java/password/pwm/ldap/PasswordChangeProgressChecker.java

@@ -24,8 +24,8 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.Data;
 import lombok.Value;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.AppProperty;
-import password.pwm.PwmDomain;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSetting;
@@ -33,8 +33,8 @@ import password.pwm.config.option.PasswordSyncCheckMode;
 import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Display;
 import password.pwm.i18n.Display;
-import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.ProgressInfo;
 import password.pwm.util.ProgressInfo;
+import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
@@ -53,6 +53,7 @@ import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
 public class PasswordChangeProgressChecker
 public class PasswordChangeProgressChecker
 {
 {
@@ -176,15 +177,9 @@ public class PasswordChangeProgressChecker
     {
     {
         final Map<String, ProgressRecord> returnValue = new LinkedHashMap<>();
         final Map<String, ProgressRecord> returnValue = new LinkedHashMap<>();
 
 
-        {
             // figure replication progress
             // figure replication progress
-            final ProgressRecord replicationProgress = figureReplicationStatusCompletion( tracker );
-            if ( replicationProgress != null )
-            {
-                returnValue.put( replicationProgress.key, replicationProgress );
-            }
-        }
-
+            figureReplicationStatusCompletion( tracker )
+                    .ifPresent( progressRecord -> returnValue.put( progressRecord.key, progressRecord ) );
 
 
         {
         {
             // random
             // random
@@ -278,7 +273,7 @@ public class PasswordChangeProgressChecker
     }
     }
 
 
 
 
-    private ProgressRecord figureReplicationStatusCompletion( final ProgressTracker tracker )
+    private Optional<ProgressRecord> figureReplicationStatusCompletion( final ProgressTracker tracker )
     {
     {
         final long initDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS ) );
         final long initDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_INIT_DELAY_MS ) );
         final long cycleDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS ) );
         final long cycleDelayMs = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_PASSWORD_REPLICA_CHECK_CYCLE_DELAY_MS ) );
@@ -288,7 +283,7 @@ public class PasswordChangeProgressChecker
         if ( passwordSyncCheckMode == PasswordSyncCheckMode.DISABLED )
         if ( passwordSyncCheckMode == PasswordSyncCheckMode.DISABLED )
         {
         {
             LOGGER.trace( pwmSession, () -> "skipping replica sync check, disabled" );
             LOGGER.trace( pwmSession, () -> "skipping replica sync check, disabled" );
-            return tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION );
+            return Optional.of( tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION ) );
         }
         }
 
 
         if ( tracker.getItemCompletions().containsKey( PROGRESS_KEY_REPLICATION ) )
         if ( tracker.getItemCompletions().containsKey( PROGRESS_KEY_REPLICATION ) )
@@ -296,7 +291,7 @@ public class PasswordChangeProgressChecker
             if ( tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION ).complete )
             if ( tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION ).complete )
             {
             {
                 LOGGER.trace( pwmSession, () -> "skipping replica sync check, replica sync completed previously" );
                 LOGGER.trace( pwmSession, () -> "skipping replica sync check, replica sync completed previously" );
-                return tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION );
+                return Optional.of( tracker.getItemCompletions().get( PROGRESS_KEY_REPLICATION ) );
             }
             }
         }
         }
 
 
@@ -305,13 +300,13 @@ public class PasswordChangeProgressChecker
             if ( TimeDuration.fromCurrent( tracker.beginTime ).isShorterThan( initialReplicaDelay ) )
             if ( TimeDuration.fromCurrent( tracker.beginTime ).isShorterThan( initialReplicaDelay ) )
             {
             {
                 LOGGER.trace( pwmSession, () -> "skipping replica sync check, initDelay has not yet passed" );
                 LOGGER.trace( pwmSession, () -> "skipping replica sync check, initDelay has not yet passed" );
-                return null;
+                return Optional.empty();
             }
             }
         }
         }
         else if ( TimeDuration.fromCurrent( tracker.lastReplicaCheckTime ).isShorterThan( cycleReplicaDelay ) )
         else if ( TimeDuration.fromCurrent( tracker.lastReplicaCheckTime ).isShorterThan( cycleReplicaDelay ) )
         {
         {
             LOGGER.trace( pwmSession, () -> "skipping replica sync check, cycleDelay has not yet passed" );
             LOGGER.trace( pwmSession, () -> "skipping replica sync check, cycleDelay has not yet passed" );
-            return null;
+            return Optional.empty();
         }
         }
 
 
         tracker.setLastReplicaCheckTime( Instant.now() );
         tracker.setLastReplicaCheckTime( Instant.now() );
@@ -324,7 +319,7 @@ public class PasswordChangeProgressChecker
             if ( checkResults.size() <= 1 )
             if ( checkResults.size() <= 1 )
             {
             {
                 LOGGER.trace( () -> "only one replica returned data, marking as complete" );
                 LOGGER.trace( () -> "only one replica returned data, marking as complete" );
-                return completedReplicationRecord;
+                return Optional.of( completedReplicationRecord );
             }
             }
             else
             else
             {
             {
@@ -344,14 +339,14 @@ public class PasswordChangeProgressChecker
                 final Percent pctComplete = Percent.of( duplicateValues + 1, checkResults.size() );
                 final Percent pctComplete = Percent.of( duplicateValues + 1, checkResults.size() );
                 final ProgressRecord progressRecord = makeReplicaProgressRecord( pctComplete );
                 final ProgressRecord progressRecord = makeReplicaProgressRecord( pctComplete );
                 LOGGER.trace( () -> "read password replication sync status as: " + JsonUtil.serialize( progressRecord ) );
                 LOGGER.trace( () -> "read password replication sync status as: " + JsonUtil.serialize( progressRecord ) );
-                return progressRecord;
+                return Optional.of( progressRecord );
             }
             }
         }
         }
         catch ( final PwmUnrecoverableException e )
         catch ( final PwmUnrecoverableException e )
         {
         {
             LOGGER.error( pwmSession, () -> "error during password replication status check: " + e.getMessage() );
             LOGGER.error( pwmSession, () -> "error during password replication status check: " + e.getMessage() );
         }
         }
-        return null;
+        return Optional.empty();
     }
     }
 
 
     private ProgressRecord makeReplicaProgressRecord( final Percent pctComplete )
     private ProgressRecord makeReplicaProgressRecord( final Percent pctComplete )

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

@@ -44,7 +44,7 @@ public abstract class AuthenticationUtility
         }
         }
         catch ( final ChaiOperationException | ChaiUnavailableException e )
         catch ( final ChaiOperationException | ChaiUnavailableException e )
         {
         {
-            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
         }
         }
     }
     }
 
 

+ 13 - 10
server/src/main/java/password/pwm/ldap/auth/LDAPAuthenticationRequest.java

@@ -70,6 +70,7 @@ import password.pwm.util.password.RandomPasswordGenerator;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.HashSet;
+import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.function.Supplier;
 
 
@@ -143,11 +144,12 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             }
             }
             else
             else
             {
             {
-                userPassword = setTempUserPassword();
-                if ( userPassword != null )
+                final Optional<PasswordData> readPassword = setTempUserPassword();
+                if ( readPassword.isPresent() )
                 {
                 {
                     strategy = AuthenticationStrategy.WRITE_THEN_BIND;
                     strategy = AuthenticationStrategy.WRITE_THEN_BIND;
                 }
                 }
+                userPassword = setTempUserPassword().orElse( null );
             }
             }
         }
         }
 
 
@@ -437,20 +439,20 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
             {
             {
                 final String errorMsg = "intruder lockout detected for user " + userIdentity + " marking session as locked out: " + e.getMessage();
                 final String errorMsg = "intruder lockout detected for user " + userIdentity + " marking session as locked out: " + e.getMessage();
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTRUDER_LDAP, errorMsg );
                 final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTRUDER_LDAP, errorMsg );
-                log( PwmLogLevel.WARN, () -> errorInformation.toDebugStr() );
+                log( PwmLogLevel.WARN, errorInformation::toDebugStr );
                 throw new PwmUnrecoverableException( errorInformation );
                 throw new PwmUnrecoverableException( errorInformation );
             }
             }
-            final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() );
+            final Optional<PwmError> pwmError = PwmError.forChaiError( e.getErrorCode() );
             final ErrorInformation errorInformation;
             final ErrorInformation errorInformation;
-            if ( pwmError != null && PwmError.ERROR_INTERNAL != pwmError )
+            if ( pwmError.isPresent() && PwmError.ERROR_INTERNAL != pwmError.get() )
             {
             {
-                errorInformation = new ErrorInformation( pwmError, e.getMessage() );
+                errorInformation = new ErrorInformation( pwmError.get(), e.getMessage() );
             }
             }
             else
             else
             {
             {
                 errorInformation = new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, "ldap error during password check: " + e.getMessage() );
                 errorInformation = new ErrorInformation( PwmError.ERROR_WRONGPASSWORD, "ldap error during password check: " + e.getMessage() );
             }
             }
-            log( PwmLogLevel.DEBUG, () -> errorInformation.toDebugStr() );
+            log( PwmLogLevel.DEBUG, errorInformation::toDebugStr );
             throw new PwmOperationalException( errorInformation );
             throw new PwmOperationalException( errorInformation );
         }
         }
         finally
         finally
@@ -477,7 +479,7 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
         return LdapOperationsHelper.readLdapPassword( pwmDomain, sessionLabel, userIdentity );
         return LdapOperationsHelper.readLdapPassword( pwmDomain, sessionLabel, userIdentity );
     }
     }
 
 
-    private PasswordData setTempUserPassword(
+    private Optional<PasswordData> setTempUserPassword(
     )
     )
             throws ChaiUnavailableException, ImpossiblePasswordPolicyException, PwmUnrecoverableException
             throws ChaiUnavailableException, ImpossiblePasswordPolicyException, PwmUnrecoverableException
     {
     {
@@ -525,9 +527,10 @@ class LDAPAuthenticationRequest implements AuthenticationRequest
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr ) );
                 throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_BAD_SESSION_PASSWORD, errorStr ) );
             }
             }
 
 
-            return currentPass;
+            return Optional.of( currentPass );
         }
         }
-        return null;
+
+        return Optional.empty();
     }
     }
 
 
     private String oraclePreTemporaryPwHandler(
     private String oraclePreTemporaryPwHandler(

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

@@ -144,7 +144,7 @@ public class SessionAuthenticator
                 } );
                 } );
                 for ( final Integer errorCode : configuredNumbers )
                 for ( final Integer errorCode : configuredNumbers )
                 {
                 {
-                    final PwmError pwmError = PwmError.forErrorNumber( errorCode );
+                    final PwmError pwmError = PwmError.forErrorNumber( errorCode ).orElse( PwmError.ERROR_INTERNAL );
                     returnSet.add( pwmError );
                     returnSet.add( pwmError );
                 }
                 }
             }
             }

+ 4 - 2
server/src/main/java/password/pwm/ldap/search/UserSearchJob.java

@@ -101,8 +101,10 @@ class UserSearchJob implements Callable<Map<UserIdentity, Map<String, String>>>
         {
         {
             if ( !userSearchJobParameters.isIgnoreOperationalErrors() )
             if ( !userSearchJobParameters.isIgnoreOperationalErrors() )
             {
             {
-                throw new PwmOperationalException( PwmError.forChaiError( e.getErrorCode() ), "ldap error during searchID="
-                        + userSearchJobParameters.getSearchID() + ", context=" + userSearchJobParameters.getContext() + ", error=" + e.getMessage() );
+                final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
+                final String msg = "ldap error during searchID="
+                        + userSearchJobParameters.getSearchID() + ", context=" + userSearchJobParameters.getContext() + ", error=" + e.getMessage();
+                throw new PwmOperationalException( pwmError, msg );
             }
             }
         }
         }
 
 

+ 4 - 3
server/src/main/java/password/pwm/svc/event/AuditService.java

@@ -61,6 +61,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
 public class AuditService extends AbstractPwmService implements PwmService
 public class AuditService extends AbstractPwmService implements PwmService
 {
 {
@@ -217,14 +218,14 @@ public class AuditService extends AbstractPwmService implements PwmService
         statisticCounterBundle.increment( DebugKey.emailsSent );
         statisticCounterBundle.increment( DebugKey.emailsSent );
     }
     }
 
 
-    public Instant eldestVaultRecord( )
+    public Optional<Instant> eldestVaultRecord( )
     {
     {
         if ( status() != STATUS.OPEN || auditVault == null )
         if ( status() != STATUS.OPEN || auditVault == null )
         {
         {
-            return null;
+            return Optional.empty();
         }
         }
 
 
-        return auditVault.oldestRecord();
+        return Optional.of( auditVault.oldestRecord() );
     }
     }
 
 
     public String sizeToDebugString( )
     public String sizeToDebugString( )

+ 2 - 101
server/src/main/java/password/pwm/svc/httpclient/ApachePwmHttpClient.java

@@ -68,7 +68,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpEntityDataType;
-import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmURL;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.bean.ImmutableByteArray;
@@ -76,7 +75,6 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
@@ -239,103 +237,6 @@ public class ApachePwmHttpClient implements AutoCloseable, PwmHttpClientProvider
         return clientBuilder.build();
         return clientBuilder.build();
     }
     }
 
 
-    String entityToDebugString(
-            final String topLine,
-            final PwmHttpClientMessage pwmHttpClientMessage
-    )
-    {
-        final HttpEntityDataType dataType = pwmHttpClientMessage.getDataType();
-        final ImmutableByteArray binaryBody = pwmHttpClientMessage.getBinaryBody();
-        final String body = pwmHttpClientMessage.getBody();
-        final Map<String, String> headers = pwmHttpClientMessage.getHeaders();
-
-        final boolean isBinary = dataType == HttpEntityDataType.ByteArray;
-        final boolean emptyBody = isBinary
-                ? binaryBody == null || binaryBody.isEmpty()
-                : StringUtil.isEmpty( body );
-
-
-        final StringBuilder msg = new StringBuilder();
-        msg.append( topLine );
-        msg.append( " id=" ).append( pwmHttpClientMessage.getRequestID() ).append( ") " );
-
-        if ( emptyBody )
-        {
-            msg.append( " (no body)" );
-        }
-
-        if ( headers != null )
-        {
-            for ( final Map.Entry<String, String> headerEntry : headers.entrySet() )
-            {
-                msg.append( "\n" );
-                final Optional<HttpHeader> httpHeader = HttpHeader.forHttpHeader( headerEntry.getKey() );
-                if ( httpHeader.isPresent() )
-                {
-                    final boolean sensitive = httpHeader.get().isSensitive();
-                    msg.append( "  header: " ).append( httpHeader.get().getHttpName() ).append( "=" );
-
-                    if ( sensitive )
-                    {
-                        msg.append( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
-                    }
-                    else
-                    {
-                        msg.append( headerEntry.getValue() );
-                    }
-                }
-                else
-                {
-                    // We encountered a header name that doesn't have a corresponding enum in HttpHeader,
-                    // so we can't check the sensitive flag.
-                    msg.append( "  header: " ).append( headerEntry.getKey() ).append( "=" ).append( headerEntry.getValue() );
-                }
-            }
-        }
-
-        if ( !emptyBody )
-        {
-            msg.append( "\n  body: " );
-
-            final boolean alwaysOutput = Boolean.parseBoolean( pwmApplication.getConfig().readAppProperty( AppProperty.HTTP_CLIENT_ALWAYS_LOG_ENTITIES ) );
-
-
-            if ( isBinary )
-            {
-                if ( binaryBody != null && !binaryBody.isEmpty() )
-                {
-                    msg.append( "[binary, " ).append( binaryBody.size() ).append( " bytes]" );
-                }
-                else
-                {
-                    msg.append( "[no data]" );
-                }
-            }
-            else
-            {
-                if ( StringUtil.isEmpty( body ) )
-                {
-                    msg.append( "[no data]" );
-                }
-                else
-                {
-                    msg.append( "[" ).append( body.length() ).append( " chars] " );
-
-                    if ( alwaysOutput || !pwmHttpClientConfiguration.isMaskBodyDebugOutput() )
-                    {
-                        msg.append( body );
-                    }
-                    else
-                    {
-                        msg.append( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
-                    }
-                }
-            }
-        }
-
-        return msg.toString();
-    }
-
     @Override
     @Override
     public PwmHttpClientResponse makeRequest(
     public PwmHttpClientResponse makeRequest(
             final PwmHttpClientRequest clientRequest,
             final PwmHttpClientRequest clientRequest,
@@ -374,7 +275,7 @@ public class ApachePwmHttpClient implements AutoCloseable, PwmHttpClientProvider
             }
             }
 
 
             LOGGER.trace( sessionLabel, () -> "client #" + clientID + " preparing to send "
             LOGGER.trace( sessionLabel, () -> "client #" + clientID + " preparing to send "
-                    + clientRequest.toDebugString( this, sslDebugText ) );
+                    + clientRequest.toDebugString( this, pwmApplication, pwmHttpClientConfiguration, sslDebugText ) );
         }
         }
 
 
         final HttpResponse httpResponse = executeRequest( clientRequest );
         final HttpResponse httpResponse = executeRequest( clientRequest );
@@ -415,7 +316,7 @@ public class ApachePwmHttpClient implements AutoCloseable, PwmHttpClientProvider
         httpClientService.getStats().increment( HttpClientService.StatsKey.responseBytes, httpClientResponse.size() );
         httpClientService.getStats().increment( HttpClientService.StatsKey.responseBytes, httpClientResponse.size() );
         LOGGER.trace( sessionLabel, () -> "client #" + clientID + " received response (id=" + clientRequest.getRequestID() + ") in "
         LOGGER.trace( sessionLabel, () -> "client #" + clientID + " received response (id=" + clientRequest.getRequestID() + ") in "
                 + duration.asCompactString() + ": "
                 + duration.asCompactString() + ": "
-                + httpClientResponse.toDebugString( this ) );
+                + httpClientResponse.toDebugString( pwmApplication, pwmHttpClientConfiguration ) );
         return httpClientResponse;
         return httpClientResponse;
     }
     }
 
 

+ 51 - 3
server/src/main/java/password/pwm/svc/httpclient/JavaPwmHttpClient.java

@@ -37,7 +37,10 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
+import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogLevel;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLContext;
@@ -57,6 +60,7 @@ import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.time.Duration;
 import java.time.Duration;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
@@ -68,10 +72,15 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( JavaPwmHttpClient.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( JavaPwmHttpClient.class );
 
 
+    private static final AtomicLoopIntIncrementer CLIENT_COUNTER = new AtomicLoopIntIncrementer();
+
     private PwmApplication pwmApplication;
     private PwmApplication pwmApplication;
     private HttpClientService httpClientService;
     private HttpClientService httpClientService;
     private HttpClient httpClient;
     private HttpClient httpClient;
     private TrustManager[] trustManagers;
     private TrustManager[] trustManagers;
+    private PwmHttpClientConfiguration pwmHttpClientConfiguration;
+
+    private final int clientID = CLIENT_COUNTER.next();
 
 
     public JavaPwmHttpClient()
     public JavaPwmHttpClient()
     {
     {
@@ -85,6 +94,7 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
         this.pwmApplication = Objects.requireNonNull( pwmApplication );
+        this.pwmHttpClientConfiguration = pwmHttpClientConfiguration;
         this.httpClientService = Objects.requireNonNull( httpClientService );
         this.httpClientService = Objects.requireNonNull( httpClientService );
         final AppConfig appConfig = pwmApplication.getConfig();
         final AppConfig appConfig = pwmApplication.getConfig();
         final HttpTrustManagerHelper trustManagerHelper = new HttpTrustManagerHelper( pwmApplication.getConfig(), pwmHttpClientConfiguration );
         final HttpTrustManagerHelper trustManagerHelper = new HttpTrustManagerHelper( pwmApplication.getConfig(), pwmHttpClientConfiguration );
@@ -136,10 +146,13 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
     {
     {
         try
         try
         {
         {
+            final Instant startTime = Instant.now();
             final HttpRequest httpRequest = makeJavaHttpRequest( clientRequest );
             final HttpRequest httpRequest = makeJavaHttpRequest( clientRequest );
             final HttpResponse<byte[]> response = httpClient.send( httpRequest, HttpResponse.BodyHandlers.ofByteArray() );
             final HttpResponse<byte[]> response = httpClient.send( httpRequest, HttpResponse.BodyHandlers.ofByteArray() );
             final Optional<HttpContentType> httpContentType = contentTypeForResponse( response.headers() );
             final Optional<HttpContentType> httpContentType = contentTypeForResponse( response.headers() );
 
 
+            logRequest( clientRequest, sessionLabel );
+
             final PwmHttpClientResponse.PwmHttpClientResponseBuilder builder = PwmHttpClientResponse.builder()
             final PwmHttpClientResponse.PwmHttpClientResponseBuilder builder = PwmHttpClientResponse.builder()
                     .statusCode( response.statusCode() )
                     .statusCode( response.statusCode() )
                     .requestID( clientRequest.getRequestID() )
                     .requestID( clientRequest.getRequestID() )
@@ -158,10 +171,11 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
                     builder.body( new String( response.body(), PwmConstants.DEFAULT_CHARSET ) );
                     builder.body( new String( response.body(), PwmConstants.DEFAULT_CHARSET ) );
                 }
                 }
             }
             }
-            httpClientService.getStats().increment( HttpClientService.StatsKey.requestBytes, clientRequest.size() );
-            StatisticsClient.incrementStat( pwmApplication, Statistic.HTTP_CLIENT_REQUESTS );
+
             final PwmHttpClientResponse pwmHttpClientResponse = builder.build();
             final PwmHttpClientResponse pwmHttpClientResponse = builder.build();
-            httpClientService.getStats().increment( HttpClientService.StatsKey.responseBytes, pwmHttpClientResponse.size() );
+
+            logResponse( clientRequest, pwmHttpClientResponse, startTime, sessionLabel );
+
             return pwmHttpClientResponse;
             return pwmHttpClientResponse;
 
 
         }
         }
@@ -172,6 +186,40 @@ public class JavaPwmHttpClient implements PwmHttpClientProvider
         }
         }
     }
     }
 
 
+    private void logRequest( final PwmHttpClientRequest clientRequest, final SessionLabel sessionLabel ) throws PwmUnrecoverableException
+    {
+        if ( LOGGER.isEnabled( PwmLogLevel.TRACE ) )
+        {
+            final String sslDebugText;
+            if ( clientRequest.isHttps() )
+            {
+                final HttpTrustManagerHelper httpTrustManagerHelper = new HttpTrustManagerHelper( pwmApplication.getConfig(), pwmHttpClientConfiguration );
+                sslDebugText = "using " + httpTrustManagerHelper.debugText();
+            }
+            else
+            {
+                sslDebugText = "";
+            }
+
+            LOGGER.trace( sessionLabel, () -> "client #" + clientID + " preparing to send "
+                    + clientRequest.toDebugString( this, pwmApplication, pwmHttpClientConfiguration, sslDebugText ) );
+        }
+    }
+
+    private void logResponse(
+            final PwmHttpClientRequest pwmHttpClientRequest,
+            final PwmHttpClientResponse pwmHttpClientResponse,
+            final Instant startTime,
+            final SessionLabel sessionLabel
+    )
+    {
+        StatisticsClient.incrementStat( pwmApplication, Statistic.HTTP_CLIENT_REQUESTS );
+        httpClientService.getStats().increment( HttpClientService.StatsKey.responseBytes, pwmHttpClientResponse.size() );
+        httpClientService.getStats().increment( HttpClientService.StatsKey.requestBytes, pwmHttpClientRequest.size() );
+        LOGGER.trace( sessionLabel, () -> "client #" + clientID + " received response (id=" + pwmHttpClientRequest.getRequestID() + ") "
+                + pwmHttpClientResponse.toDebugString( pwmApplication, pwmHttpClientConfiguration ), () -> TimeDuration.fromCurrent( startTime ) );
+    }
+
     private static Optional<HttpContentType> contentTypeForResponse( final HttpHeaders httpHeaders )
     private static Optional<HttpContentType> contentTypeForResponse( final HttpHeaders httpHeaders )
     {
     {
         if ( httpHeaders != null )
         if ( httpHeaders != null )

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

@@ -43,18 +43,18 @@ public interface PwmHttpClient extends AutoCloseable
             PwmHttpClientRequest clientRequest,
             PwmHttpClientRequest clientRequest,
             SessionLabel sessionLabel
             SessionLabel sessionLabel
     )
     )
-                    throws PwmUnrecoverableException;
+            throws PwmUnrecoverableException;
 
 
     InputStream streamForUrl( String inputUrl )
     InputStream streamForUrl( String inputUrl )
-                            throws IOException, PwmUnrecoverableException;
+            throws IOException, PwmUnrecoverableException;
 
 
     List<X509Certificate> readServerCertificates()
     List<X509Certificate> readServerCertificates()
-                                    throws PwmUnrecoverableException;
+            throws PwmUnrecoverableException;
 
 
     static List<X509Certificate> readServerCertificates( final TrustManager[] trustManagers )
     static List<X509Certificate> readServerCertificates( final TrustManager[] trustManagers )
             throws PwmUnrecoverableException
             throws PwmUnrecoverableException
     {
     {
-        final List<X509Certificate> returnList = new ArrayList<>(  );
+        final List<X509Certificate> returnList = new ArrayList<>();
         if ( trustManagers != null )
         if ( trustManagers != null )
         {
         {
             for ( final TrustManager trustManager : trustManagers )
             for ( final TrustManager trustManager : trustManagers )

+ 148 - 1
server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientMessage.java

@@ -20,12 +20,18 @@
 
 
 package password.pwm.svc.httpclient;
 package password.pwm.svc.httpclient;
 
 
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpEntityDataType;
+import password.pwm.http.HttpHeader;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.bean.ImmutableByteArray;
+import password.pwm.util.java.StringUtil;
 
 
 import java.util.Map;
 import java.util.Map;
+import java.util.Optional;
 
 
-public interface PwmHttpClientMessage
+interface PwmHttpClientMessage
 {
 {
     int getRequestID();
     int getRequestID();
 
 
@@ -36,4 +42,145 @@ public interface PwmHttpClientMessage
     HttpEntityDataType getDataType();
     HttpEntityDataType getDataType();
 
 
     ImmutableByteArray getBinaryBody();
     ImmutableByteArray getBinaryBody();
+
+    default boolean isBinary()
+    {
+        return getDataType() == HttpEntityDataType.ByteArray;
+    }
+
+    default boolean hasBody()
+    {
+        return isBinary()
+                ? getBinaryBody() != null && !getBinaryBody().isEmpty()
+                : !StringUtil.isEmpty( getBody() );
+    }
+
+    static long sizeImpl( final PwmHttpClientMessage pwmHttpClientMessage )
+    {
+        long size = pwmHttpClientMessage.getBody() == null ? 0 : pwmHttpClientMessage.getBody().length();
+        size += pwmHttpClientMessage.getBinaryBody() == null ? 0 : pwmHttpClientMessage.getBinaryBody().size();
+        if ( pwmHttpClientMessage.getHeaders() != null )
+        {
+            size += pwmHttpClientMessage.getHeaders().entrySet().stream()
+                    .map( e -> e.getValue().length() + e.getKey().length() )
+                    .reduce( 0, Integer::sum );
+        }
+        return size;
+    }
+
+    static String entityToDebugString(
+            final String topLine,
+            final PwmApplication pwmApplication,
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration,
+            final PwmHttpClientMessage pwmHttpClientMessage
+    )
+    {
+        final boolean emptyBody = !pwmHttpClientMessage.hasBody();
+        final StringBuilder msg = new StringBuilder();
+
+        if ( topLine != null )
+        {
+            msg.append( topLine );
+        }
+
+        msg.append( " id=" ).append( pwmHttpClientMessage.getRequestID() ).append( ") " );
+
+        if ( emptyBody )
+        {
+            msg.append( " (no body)" );
+        }
+
+        msg.append( outputHeadersToLog( pwmHttpClientMessage ) );
+
+        if ( !emptyBody )
+        {
+            msg.append( outputBodyToLog( pwmHttpClientMessage, pwmApplication, pwmHttpClientConfiguration ) );
+        }
+
+        return msg.toString();
+    }
+
+    private static String outputHeadersToLog( final PwmHttpClientMessage pwmHttpClientMessage )
+    {
+        final Map<String, String> headers = pwmHttpClientMessage.getHeaders();
+
+        if ( headers != null )
+        {
+            final StringBuilder msg = new StringBuilder();
+            for ( final Map.Entry<String, String> headerEntry : headers.entrySet() )
+            {
+                msg.append( "\n" );
+                final Optional<HttpHeader> httpHeader = HttpHeader.forHttpHeader( headerEntry.getKey() );
+                if ( httpHeader.isPresent() )
+                {
+                    final boolean sensitive = httpHeader.get().isSensitive();
+                    msg.append( "  header: " ).append( httpHeader.get().getHttpName() ).append( "=" );
+
+                    if ( sensitive )
+                    {
+                        msg.append( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
+                    }
+                    else
+                    {
+                        msg.append( headerEntry.getValue() );
+                    }
+                }
+                else
+                {
+                    // We encountered a header name that doesn't have a corresponding enum in HttpHeader,
+                    // so we can't check the sensitive flag.
+                    msg.append( "  header: " ).append( headerEntry.getKey() ).append( "=" ).append( headerEntry.getValue() );
+                }
+            }
+            return msg.toString();
+        }
+
+        return "";
+    }
+
+    private static String outputBodyToLog(
+            final PwmHttpClientMessage pwmHttpClientMessage,
+            final PwmApplication pwmApplication,
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration
+    )
+    {
+        final StringBuilder msg = new StringBuilder( "\n  body: " );
+
+        if ( pwmHttpClientMessage.isBinary() )
+        {
+            final ImmutableByteArray binaryBody = pwmHttpClientMessage.getBinaryBody();
+            if ( binaryBody != null && !binaryBody.isEmpty() )
+            {
+                msg.append( "[binary, " ).append( binaryBody.size() ).append( " bytes]" );
+            }
+            else
+            {
+                msg.append( "[no data]" );
+            }
+        }
+        else
+        {
+            final String body = pwmHttpClientMessage.getBody();
+            if ( StringUtil.isEmpty( body ) )
+            {
+                msg.append( "[no data]" );
+            }
+            else
+            {
+                msg.append( "[" ).append( body.length() ).append( " chars] " );
+
+                final boolean alwaysOutput = pwmApplication.getConfig().readBooleanAppProperty( AppProperty.HTTP_CLIENT_ALWAYS_LOG_ENTITIES );
+                if ( alwaysOutput || !pwmHttpClientConfiguration.isMaskBodyDebugOutput() )
+                {
+                    msg.append( body );
+                }
+                else
+                {
+                    msg.append( PwmConstants.LOG_REMOVED_VALUE_REPLACEMENT );
+                }
+            }
+        }
+
+        return msg.toString();
+    }
 }
 }

+ 12 - 14
server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientRequest.java

@@ -23,6 +23,7 @@ package password.pwm.svc.httpclient;
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Singular;
 import lombok.Singular;
 import lombok.Value;
 import lombok.Value;
+import password.pwm.PwmApplication;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.bean.ImmutableByteArray;
@@ -55,13 +56,18 @@ public class PwmHttpClientRequest implements Serializable, PwmHttpClientMessage
     @Singular
     @Singular
     private final Map<String, String> headers;
     private final Map<String, String> headers;
 
 
-    public String toDebugString( final ApachePwmHttpClient pwmHttpClient, final String additionalText )
+    String toDebugString(
+            final PwmHttpClient pwmHttpClient,
+            final PwmApplication pwmApplication,
+            final PwmHttpClientConfiguration pwmHttpClientConfiguration,
+            final String additionalText
+    )
     {
     {
         final String topLine = "HTTP " + method + " request to " + url
         final String topLine = "HTTP " + method + " request to " + url
                 + ( StringUtil.isEmpty( additionalText )
                 + ( StringUtil.isEmpty( additionalText )
                 ? ""
                 ? ""
-                : " " + additionalText );
-        return pwmHttpClient.entityToDebugString( topLine, this );
+                : " " + ( additionalText == null ? "" : additionalText ) );
+        return PwmHttpClientMessage.entityToDebugString( topLine, pwmApplication, pwmHttpClientConfiguration, this );
     }
     }
 
 
     public boolean isHttps()
     public boolean isHttps()
@@ -71,17 +77,9 @@ public class PwmHttpClientRequest implements Serializable, PwmHttpClientMessage
 
 
     public long size()
     public long size()
     {
     {
-        long size = 0;
-        size += method.toString().length();
-        size += url.length();
-        size += body == null ? 0 : body.length();
-        size += binaryBody == null ? 0 : binaryBody.size();
-        if ( headers != null )
-        {
-            size += headers.entrySet().stream()
-                    .map( e -> e.getValue().length() + e.getKey().length() )
-                    .reduce( 0, Integer::sum );
-        }
+        long size = PwmHttpClientMessage.sizeImpl( this );
+        size += method == null ? 0 : method.toString().length();
+        size += url == null ? 0 : url.length();
         return size;
         return size;
     }
     }
 }
 }

+ 5 - 12
server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientResponse.java

@@ -22,6 +22,7 @@ package password.pwm.svc.httpclient;
 
 
 import lombok.Builder;
 import lombok.Builder;
 import lombok.Value;
 import lombok.Value;
+import password.pwm.PwmApplication;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.http.bean.ImmutableByteArray;
@@ -46,24 +47,16 @@ public class PwmHttpClientResponse implements Serializable, PwmHttpClientMessage
     private final String body;
     private final String body;
     private final ImmutableByteArray binaryBody;
     private final ImmutableByteArray binaryBody;
 
 
-    public String toDebugString( final ApachePwmHttpClient pwmHttpClient )
+    String toDebugString( final PwmApplication pwmApplication, final PwmHttpClientConfiguration pwmHttpClientConfiguration )
     {
     {
         final String topLine = "HTTP response status " + statusCode + " " + statusPhrase;
         final String topLine = "HTTP response status " + statusCode + " " + statusPhrase;
-        return pwmHttpClient.entityToDebugString( topLine, this );
+        return PwmHttpClientMessage.entityToDebugString( topLine, pwmApplication, pwmHttpClientConfiguration, this );
     }
     }
 
 
-    public long size()
+    long size()
     {
     {
-        long size = 0;
+        long size = PwmHttpClientMessage.sizeImpl( this );
         size += statusPhrase == null ? 0 : statusPhrase.length();
         size += statusPhrase == null ? 0 : statusPhrase.length();
-        size += body == null ? 0 : body.length();
-        size += binaryBody == null ? 0 : binaryBody.size();
-        if ( headers != null )
-        {
-            size += headers.entrySet().stream()
-                    .map( e -> e.getValue().length() + e.getKey().length() )
-                    .reduce( 0, Integer::sum );
-        }
         return size;
         return size;
     }
     }
 }
 }

+ 6 - 5
server/src/main/java/password/pwm/svc/intruder/IntruderDomainService.java

@@ -70,6 +70,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
+import java.util.Optional;
 
 
 public class IntruderDomainService extends AbstractPwmService implements PwmService
 public class IntruderDomainService extends AbstractPwmService implements PwmService
 {
 {
@@ -331,7 +332,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
                             sessionLabel
                             sessionLabel
                     );
                     );
                     AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
                     AuditServiceClient.submit( pwmDomain.getPwmApplication(), sessionLabel, auditRecord );
-                    sendAlert( manager.readIntruderRecord( subject ), sessionLabel );
+                    manager.readIntruderRecord( subject ).ifPresent( record -> sendAlert( record, sessionLabel ) );
                 }
                 }
                 else
                 else
                 {
                 {
@@ -352,7 +353,7 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
             throw e;
             throw e;
         }
         }
 
 
-        delayPenalty( manager.readIntruderRecord( subject ), sessionLabel );
+        manager.readIntruderRecord( subject ).ifPresent( record -> delayPenalty( record, sessionLabel ) );
     }
     }
 
 
     private void delayPenalty( final IntruderRecord intruderRecord, final SessionLabel sessionLabel )
     private void delayPenalty( final IntruderRecord intruderRecord, final SessionLabel sessionLabel )
@@ -451,12 +452,12 @@ public class IntruderDomainService extends AbstractPwmService implements PwmServ
             return 0;
             return 0;
         }
         }
 
 
-        final IntruderRecord intruderRecord = recordManagers.get( IntruderRecordType.ADDRESS ).readIntruderRecord( srcAddress );
-        if ( intruderRecord == null )
+        final Optional<IntruderRecord> intruderRecord = recordManagers.get( IntruderRecordType.ADDRESS ).readIntruderRecord( srcAddress );
+        if ( intruderRecord.isEmpty() )
         {
         {
             return 0;
             return 0;
         }
         }
 
 
-        return intruderRecord.getAttemptCount();
+        return intruderRecord.get().getAttemptCount();
     }
     }
 }
 }

+ 3 - 1
server/src/main/java/password/pwm/svc/intruder/IntruderRecordManager.java

@@ -23,6 +23,8 @@ package password.pwm.svc.intruder;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmException;
 import password.pwm.util.java.ClosableIterator;
 import password.pwm.util.java.ClosableIterator;
 
 
+import java.util.Optional;
+
 public interface IntruderRecordManager
 public interface IntruderRecordManager
 {
 {
     boolean checkSubject( String subject );
     boolean checkSubject( String subject );
@@ -35,7 +37,7 @@ public interface IntruderRecordManager
 
 
     void markAlerted( String subject );
     void markAlerted( String subject );
 
 
-    IntruderRecord readIntruderRecord( String subject );
+    Optional<IntruderRecord> readIntruderRecord( String subject );
 
 
     ClosableIterator<IntruderRecord> iterator( ) throws PwmException;
     ClosableIterator<IntruderRecord> iterator( ) throws PwmException;
 }
 }

+ 23 - 24
server/src/main/java/password/pwm/svc/intruder/IntruderRecordManagerImpl.java

@@ -35,6 +35,8 @@ import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmHashAlgorithm;
 import password.pwm.util.secure.PwmHashAlgorithm;
 
 
+import java.util.Optional;
+
 class IntruderRecordManagerImpl implements IntruderRecordManager
 class IntruderRecordManagerImpl implements IntruderRecordManager
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( IntruderRecordManagerImpl.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( IntruderRecordManagerImpl.class );
@@ -69,16 +71,18 @@ class IntruderRecordManagerImpl implements IntruderRecordManager
             throw new IllegalArgumentException( "subject is required value" );
             throw new IllegalArgumentException( "subject is required value" );
         }
         }
 
 
-        final IntruderRecord record = readIntruderRecord( subject );
-        if ( record == null )
+        final Optional<IntruderRecord> record = readIntruderRecord( subject );
+        if ( record.isEmpty() )
         {
         {
             return false;
             return false;
         }
         }
-        if ( TimeDuration.fromCurrent( record.getTimeStamp() ).isLongerThan( settings.getCheckDuration() ) )
+
+        if ( TimeDuration.fromCurrent( record.get().getTimeStamp() ).isLongerThan( settings.getCheckDuration() ) )
         {
         {
             return false;
             return false;
         }
         }
-        if ( record.getAttemptCount() >= settings.getCheckCount() )
+
+        if ( record.get().getAttemptCount() >= settings.getCheckCount() )
         {
         {
             return true;
             return true;
         }
         }
@@ -93,12 +97,7 @@ class IntruderRecordManagerImpl implements IntruderRecordManager
             throw new IllegalArgumentException( "subject is required value" );
             throw new IllegalArgumentException( "subject is required value" );
         }
         }
 
 
-        IntruderRecord record = readIntruderRecord( subject );
-
-        if ( record == null )
-        {
-            record = new IntruderRecord( domainID, recordType, subject );
-        }
+        IntruderRecord record = readIntruderRecord( subject ).orElse( new IntruderRecord( domainID, recordType, subject ) );
 
 
         final TimeDuration age = TimeDuration.fromCurrent( record.getTimeStamp() );
         final TimeDuration age = TimeDuration.fromCurrent( record.getTimeStamp() );
         if ( age.isLongerThan( settings.getCheckDuration() ) )
         if ( age.isLongerThan( settings.getCheckDuration() ) )
@@ -116,52 +115,52 @@ class IntruderRecordManagerImpl implements IntruderRecordManager
     @Override
     @Override
     public void clearSubject( final String subject )
     public void clearSubject( final String subject )
     {
     {
-        final IntruderRecord record = readIntruderRecord( subject );
-        if ( record == null )
+        final Optional<IntruderRecord> record = readIntruderRecord( subject );
+        if ( record.isEmpty() )
         {
         {
             return;
             return;
         }
         }
 
 
-        if ( record.getAttemptCount() == 0 )
+        if ( record.get().getAttemptCount() == 0 )
         {
         {
             return;
             return;
         }
         }
 
 
-        record.clearAttemptCount();
-        writeIntruderRecord( record );
+        record.get().clearAttemptCount();
+        writeIntruderRecord( record.get() );
     }
     }
 
 
     @Override
     @Override
     public boolean isAlerted( final String subject )
     public boolean isAlerted( final String subject )
     {
     {
-        final IntruderRecord record = readIntruderRecord( subject );
-        return record != null && record.isAlerted();
+        final Optional<IntruderRecord> record = readIntruderRecord( subject );
+        return record.isPresent() && record.get().isAlerted();
     }
     }
 
 
     @Override
     @Override
     public void markAlerted( final String subject )
     public void markAlerted( final String subject )
     {
     {
-        final IntruderRecord record = readIntruderRecord( subject );
-        if ( record == null || record.isAlerted() )
+        final Optional<IntruderRecord> record = readIntruderRecord( subject );
+        if ( record.isEmpty() || record.get().isAlerted() )
         {
         {
             return;
             return;
         }
         }
-        record.setAlerted( true );
-        writeIntruderRecord( record );
+        record.get().setAlerted( true );
+        writeIntruderRecord( record.get() );
     }
     }
 
 
     @Override
     @Override
-    public IntruderRecord readIntruderRecord( final String subject )
+    public Optional<IntruderRecord> readIntruderRecord( final String subject )
     {
     {
         try
         try
         {
         {
-            return recordStore.read( makeKey( subject ) ).orElse( null );
+            return Optional.ofNullable( recordStore.read( makeKey( subject ) ).orElse( null ) );
         }
         }
         catch ( final PwmException e )
         catch ( final PwmException e )
         {
         {
             LOGGER.error( () -> "unable to read read intruder record from storage: " + e.getMessage() );
             LOGGER.error( () -> "unable to read read intruder record from storage: " + e.getMessage() );
         }
         }
-        return null;
+        return Optional.empty();
     }
     }
 
 
     private void writeIntruderRecord( final IntruderRecord intruderRecord )
     private void writeIntruderRecord( final IntruderRecord intruderRecord )

+ 3 - 2
server/src/main/java/password/pwm/svc/intruder/StubRecordManager.java

@@ -24,6 +24,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.util.java.ClosableIterator;
 import password.pwm.util.java.ClosableIterator;
 
 
 import java.util.NoSuchElementException;
 import java.util.NoSuchElementException;
+import java.util.Optional;
 
 
 class StubRecordManager implements IntruderRecordManager
 class StubRecordManager implements IntruderRecordManager
 {
 {
@@ -55,9 +56,9 @@ class StubRecordManager implements IntruderRecordManager
     }
     }
 
 
     @Override
     @Override
-    public IntruderRecord readIntruderRecord( final String subject )
+    public Optional<IntruderRecord> readIntruderRecord( final String subject )
     {
     {
-        return null;
+        return Optional.empty();
     }
     }
 
 
     @Override
     @Override

+ 2 - 2
server/src/main/java/password/pwm/svc/userhistory/LdapXmlUserHistory.java

@@ -93,7 +93,7 @@ public class LdapXmlUserHistory implements UserHistoryStore
         }
         }
         catch ( final ChaiUnavailableException e )
         catch ( final ChaiUnavailableException e )
         {
         {
-            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
         }
         }
     }
     }
 
 
@@ -191,7 +191,7 @@ public class LdapXmlUserHistory implements UserHistoryStore
         }
         }
         catch ( final ChaiUnavailableException e )
         catch ( final ChaiUnavailableException e )
         {
         {
-            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+            throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
         }
         }
     }
     }
 
 

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

@@ -371,7 +371,7 @@ public class FormUtility
                     }
                     }
                     catch ( final ChaiOperationException | ChaiUnavailableException e )
                     catch ( final ChaiOperationException | ChaiUnavailableException e )
                     {
                     {
-                        final PwmError error = PwmError.forChaiError( e.getErrorCode() );
+                        final PwmError error = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
                         throw new PwmUnrecoverableException( error.toInfo() );
                         throw new PwmUnrecoverableException( error.toInfo() );
                     }
                     }
 
 
@@ -563,15 +563,15 @@ public class FormUtility
             PwmError error = null;
             PwmError error = null;
             if ( e instanceof ChaiException )
             if ( e instanceof ChaiException )
             {
             {
-                error = PwmError.forChaiError( ( ( ChaiException ) e ).getErrorCode() );
+                error = PwmError.forChaiError( ( ( ChaiException ) e ).getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
             }
             }
-            if ( error == null || error == PwmError.ERROR_INTERNAL )
+            if ( error == PwmError.ERROR_INTERNAL )
             {
             {
                 error = PwmError.ERROR_LDAP_DATA_ERROR;
                 error = PwmError.ERROR_LDAP_DATA_ERROR;
             }
             }
 
 
             final ErrorInformation errorInformation = new ErrorInformation( error, "error reading current profile values: " + e.getMessage() );
             final ErrorInformation errorInformation = new ErrorInformation( error, "error reading current profile values: " + e.getMessage() );
-            LOGGER.error( sessionLabel, () -> errorInformation.getDetailedErrorMsg() );
+            LOGGER.error( sessionLabel, errorInformation::getDetailedErrorMsg );
             throw new PwmUnrecoverableException( errorInformation );
             throw new PwmUnrecoverableException( errorInformation );
         }
         }
 
 

+ 12 - 11
server/src/main/java/password/pwm/util/logging/LocalDBLogger.java

@@ -49,6 +49,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Queue;
 import java.util.Queue;
 import java.util.TreeMap;
 import java.util.TreeMap;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -167,12 +168,12 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
     }
     }
 
 
 
 
-    public Instant getTailDate( )
+    public Optional<Instant> getTailDate( )
     {
     {
         final PwmLogEvent loopEvent;
         final PwmLogEvent loopEvent;
         if ( localDBListQueue.isEmpty() )
         if ( localDBListQueue.isEmpty() )
         {
         {
-            return null;
+            return Optional.empty();
         }
         }
         try
         try
         {
         {
@@ -182,7 +183,7 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
                 final Instant tailDate = loopEvent.getTimestamp();
                 final Instant tailDate = loopEvent.getTimestamp();
                 if ( tailDate != null )
                 if ( tailDate != null )
                 {
                 {
-                    return tailDate;
+                    return Optional.of( tailDate );
                 }
                 }
             }
             }
         }
         }
@@ -191,7 +192,7 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
             LOGGER.error( () -> "unexpected error attempting to determine tail event timestamp: " + e.getMessage() );
             LOGGER.error( () -> "unexpected error attempting to determine tail event timestamp: " + e.getMessage() );
         }
         }
 
 
-        return null;
+        return Optional.empty();
     }
     }
 
 
     private void scheduleNextFlush()
     private void scheduleNextFlush()
@@ -216,7 +217,7 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
     {
     {
         final Map<String, String> debugData = new TreeMap<>();
         final Map<String, String> debugData = new TreeMap<>();
         {
         {
-            final Instant tailAge = getTailDate();
+            final Instant tailAge = getTailDate().orElse( null );
             debugData.put( "EventsTailAge", tailAge == null ? "n/a" : TimeDuration.fromCurrent( tailAge ).asCompactString() );
             debugData.put( "EventsTailAge", tailAge == null ? "n/a" : TimeDuration.fromCurrent( tailAge ).asCompactString() );
         }
         }
 
 
@@ -280,14 +281,14 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
         }
         }
 
 
         // purge the tail if it is missing or has invalid timestamp
         // purge the tail if it is missing or has invalid timestamp
-        final Instant tailTimestamp = getTailDate();
-        if ( tailTimestamp == null )
+        final Optional<Instant> tailTimestamp = getTailDate();
+        if ( tailTimestamp.isEmpty() )
         {
         {
             return 1;
             return 1;
         }
         }
 
 
         // purge excess events by age;
         // purge excess events by age;
-        final TimeDuration tailAge = TimeDuration.fromCurrent( tailTimestamp );
+        final TimeDuration tailAge = TimeDuration.fromCurrent( tailTimestamp.get() );
         if ( tailAge.isLongerThan( settings.getMaxAge() ) )
         if ( tailAge.isLongerThan( settings.getMaxAge() ) )
         {
         {
             final long maxRemovalPercentageOfSize = getStoredEventCount() / maxTrailSize;
             final long maxRemovalPercentageOfSize = getStoredEventCount() / maxTrailSize;
@@ -574,10 +575,10 @@ public class LocalDBLogger extends AbstractPwmService implements PwmService
             );
             );
         }
         }
 
 
-        final Instant tailDate = getTailDate();
-        if ( tailDate != null )
+        final Optional<Instant> tailDate = getTailDate();
+        if ( tailDate.isPresent() )
         {
         {
-            final TimeDuration timeDuration = TimeDuration.fromCurrent( tailDate );
+            final TimeDuration timeDuration = TimeDuration.fromCurrent( tailDate.get() );
             final TimeDuration maxTimeDuration = settings.getMaxAge().add( TimeDuration.HOUR );
             final TimeDuration maxTimeDuration = settings.getMaxAge().add( TimeDuration.HOUR );
             if ( timeDuration.isLongerThan( maxTimeDuration ) )
             if ( timeDuration.isLongerThan( maxTimeDuration ) )
             {
             {

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

@@ -458,14 +458,14 @@ public class PasswordUtility
         catch ( final ChaiPasswordPolicyException e )
         catch ( final ChaiPasswordPolicyException e )
         {
         {
             final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.toString();
             final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.toString();
-            final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() );
-            final ErrorInformation error = new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError, errorMsg );
+            final Optional<PwmError> pwmError = PwmError.forChaiError( e.getErrorCode() );
+            final ErrorInformation error = new ErrorInformation( pwmError.orElse( PwmError.PASSWORD_UNKNOWN_VALIDATION ), errorMsg );
             throw new PwmOperationalException( error );
             throw new PwmOperationalException( error );
         }
         }
         catch ( final ChaiOperationException e )
         catch ( final ChaiOperationException e )
         {
         {
             final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.getMessage();
             final String errorMsg = "error setting password for user '" + userIdentity.toDisplayString() + "'' " + e.getMessage();
-            final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() ) == null ? PwmError.ERROR_INTERNAL : PwmError.forChaiError( e.getErrorCode() );
+            final PwmError pwmError = PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL );
             final ErrorInformation error = new ErrorInformation( pwmError, errorMsg );
             final ErrorInformation error = new ErrorInformation( pwmError, errorMsg );
             throw new PwmOperationalException( error );
             throw new PwmOperationalException( error );
         }
         }
@@ -1016,7 +1016,7 @@ public class PasswordUtility
             }
             }
             catch ( final ChaiUnavailableException e )
             catch ( final ChaiUnavailableException e )
             {
             {
-                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ) );
+                throw new PwmUnrecoverableException( PwmError.forChaiError( e.getErrorCode() ).orElse( PwmError.ERROR_INTERNAL ) );
             }
             }
             if ( chaiPolicy != null )
             if ( chaiPolicy != null )
             {
             {

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

@@ -145,8 +145,8 @@ public class PwmPasswordRuleValidator
             catch ( final ChaiPasswordPolicyException e )
             catch ( final ChaiPasswordPolicyException e )
             {
             {
                 final ChaiError passwordError = e.getErrorCode();
                 final ChaiError passwordError = e.getErrorCode();
-                final PwmError pwmError = PwmError.forChaiError( passwordError );
-                final ErrorInformation info = new ErrorInformation( pwmError == null ? PwmError.PASSWORD_UNKNOWN_VALIDATION : pwmError );
+                final PwmError pwmError = PwmError.forChaiError( passwordError ).orElse( PwmError.PASSWORD_UNKNOWN_VALIDATION );
+                final ErrorInformation info = new ErrorInformation( pwmError );
                 LOGGER.trace( () -> "ChaiPasswordPolicyException was thrown while validating password: " + e.toString() );
                 LOGGER.trace( () -> "ChaiPasswordPolicyException was thrown while validating password: " + e.toString() );
                 errorResults.add( info );
                 errorResults.add( info );
             }
             }

+ 2 - 2
server/src/main/java/password/pwm/util/secure/SecureEngine.java

@@ -218,7 +218,7 @@ public class SecureEngine
                 if ( workingValue.length <= checksumSize )
                 if ( workingValue.length <= checksumSize )
                 {
                 {
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CRYPT_ERROR,
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CRYPT_ERROR,
-                            "incoming " + blockAlgorithm.toString() + " data is missing checksum" ) );
+                            "incoming " + blockAlgorithm + " data is missing checksum" ) );
                 }
                 }
                 final byte[] inputChecksum = Arrays.copyOfRange( workingValue, 0, checksumSize );
                 final byte[] inputChecksum = Arrays.copyOfRange( workingValue, 0, checksumSize );
                 final byte[] inputPayload = Arrays.copyOfRange( workingValue, checksumSize, workingValue.length );
                 final byte[] inputPayload = Arrays.copyOfRange( workingValue, checksumSize, workingValue.length );
@@ -226,7 +226,7 @@ public class SecureEngine
                 if ( !Arrays.equals( inputChecksum, computedChecksum ) )
                 if ( !Arrays.equals( inputChecksum, computedChecksum ) )
                 {
                 {
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CRYPT_ERROR,
                     throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CRYPT_ERROR,
-                            "incoming " + blockAlgorithm.toString() + " data has incorrect checksum" ) );
+                            "incoming " + blockAlgorithm + " data has incorrect checksum" ) );
                 }
                 }
                 workingValue = inputPayload;
                 workingValue = inputPayload;
             }
             }

+ 5 - 6
server/src/main/java/password/pwm/ws/server/RestUtility.java

@@ -33,7 +33,6 @@ import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.StringUtil;
 
 
-import java.io.IOException;
 import java.util.Optional;
 import java.util.Optional;
 
 
 public class RestUtility
 public class RestUtility
@@ -44,7 +43,7 @@ public class RestUtility
     }
     }
 
 
     public static <T> T deserializeJsonBody( final RestRequest restRequest, final Class<T> classOfT, final Flag... flags )
     public static <T> T deserializeJsonBody( final RestRequest restRequest, final Class<T> classOfT, final Flag... flags )
-            throws IOException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
     {
         try
         try
         {
         {
@@ -156,7 +155,7 @@ public class RestUtility
         optional
         optional
     }
     }
 
 
-    public static String readValueFromJsonAndParam(
+    public static Optional<String> readValueFromJsonAndParam(
             final String jsonValue,
             final String jsonValue,
             final String paramValue,
             final String paramValue,
             final String paramName,
             final String paramName,
@@ -168,7 +167,7 @@ public class RestUtility
         {
         {
             if ( JavaHelper.enumArrayContainsValue( flags, ReadValueFlag.optional ) )
             if ( JavaHelper.enumArrayContainsValue( flags, ReadValueFlag.optional ) )
             {
             {
-                return null;
+                return Optional.empty();
             }
             }
             final String msg = paramName + " parameter is not specified";
             final String msg = paramName + " parameter is not specified";
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, msg, new String[]
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_PARAMETER, msg, new String[]
@@ -185,9 +184,9 @@ public class RestUtility
         }
         }
         else if ( StringUtil.notEmpty( jsonValue ) )
         else if ( StringUtil.notEmpty( jsonValue ) )
         {
         {
-            return jsonValue;
+            return Optional.of( jsonValue );
         }
         }
 
 
-        return paramValue;
+        return Optional.of( paramValue );
     }
     }
 }
 }

+ 2 - 2
server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java

@@ -242,7 +242,7 @@ public class RestChallengesServer extends RestServlet
 
 
     @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.json, produces = HttpContentType.json )
     @RestMethodHandler( method = HttpMethod.POST, consumes = HttpContentType.json, produces = HttpContentType.json )
     public RestResultBean doSetChallengeDataJson( final RestRequest restRequest )
     public RestResultBean doSetChallengeDataJson( final RestRequest restRequest )
-            throws IOException, PwmUnrecoverableException
+            throws  PwmUnrecoverableException
     {
     {
         final JsonChallengesData jsonInput = RestUtility.deserializeJsonBody( restRequest, JsonChallengesData.class );
         final JsonChallengesData jsonInput = RestUtility.deserializeJsonBody( restRequest, JsonChallengesData.class );
 
 
@@ -251,7 +251,7 @@ public class RestChallengesServer extends RestServlet
                 restRequest.readParameterAsString( FIELD_USERNAME, PwmHttpRequestWrapper.Flag.BypassValidation ),
                 restRequest.readParameterAsString( FIELD_USERNAME, PwmHttpRequestWrapper.Flag.BypassValidation ),
                 FIELD_USERNAME,
                 FIELD_USERNAME,
                 RestUtility.ReadValueFlag.optional
                 RestUtility.ReadValueFlag.optional
-        );
+        ).orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_FIELD_REQUIRED, FIELD_USERNAME ) );
 
 
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
 

+ 3 - 3
server/src/main/java/password/pwm/ws/server/rest/RestCheckPasswordServer.java

@@ -139,18 +139,18 @@ public class RestCheckPasswordServer extends RestServlet
                             jsonBody == null ? null : jsonBody.getPassword1(),
                             jsonBody == null ? null : jsonBody.getPassword1(),
                             restRequest.readParameterAsString( FIELD_PASSWORD_1 ),
                             restRequest.readParameterAsString( FIELD_PASSWORD_1 ),
                             FIELD_PASSWORD_1
                             FIELD_PASSWORD_1
-                    ),
+                    ).orElse( null ),
                     RestUtility.readValueFromJsonAndParam(
                     RestUtility.readValueFromJsonAndParam(
                             jsonBody == null ? null : jsonBody.getPassword2(),
                             jsonBody == null ? null : jsonBody.getPassword2(),
                             restRequest.readParameterAsString( FIELD_PASSWORD_2 ),
                             restRequest.readParameterAsString( FIELD_PASSWORD_2 ),
                             FIELD_PASSWORD_2
                             FIELD_PASSWORD_2
-                    ),
+                    ).orElse( null ),
                     RestUtility.readValueFromJsonAndParam(
                     RestUtility.readValueFromJsonAndParam(
                             jsonBody == null ? null : jsonBody.getUsername(),
                             jsonBody == null ? null : jsonBody.getUsername(),
                             restRequest.readParameterAsString( FIELD_USERNAME ),
                             restRequest.readParameterAsString( FIELD_USERNAME ),
                             FIELD_USERNAME,
                             FIELD_USERNAME,
                             RestUtility.ReadValueFlag.optional
                             RestUtility.ReadValueFlag.optional
-                    )
+                    ).orElse( null )
             );
             );
         }
         }
 
 

+ 5 - 11
server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java

@@ -39,9 +39,9 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.servlet.updateprofile.UpdateProfileUtil;
 import password.pwm.http.servlet.updateprofile.UpdateProfileUtil;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.svc.stats.StatisticsClient;
 import password.pwm.util.FormMap;
 import password.pwm.util.FormMap;
@@ -61,7 +61,6 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 import java.util.Set;
 
 
 @WebServlet(
 @WebServlet(
@@ -178,19 +177,14 @@ public class RestProfileServer extends RestServlet
     private static UpdateProfileProfile getProfile( final RestRequest restRequest, final TargetUserIdentity targetUserIdentity )
     private static UpdateProfileProfile getProfile( final RestRequest restRequest, final TargetUserIdentity targetUserIdentity )
         throws PwmUnrecoverableException
         throws PwmUnrecoverableException
     {
     {
-        final Optional<String> updateProfileID = ProfileUtility.discoverProfileIDForUser(
+        final String updateProfileID = ProfileUtility.discoverProfileIDForUser(
             restRequest.getDomain(),
             restRequest.getDomain(),
             restRequest.getSessionLabel(),
             restRequest.getSessionLabel(),
             targetUserIdentity.getUserIdentity(),
             targetUserIdentity.getUserIdentity(),
             ProfileDefinition.UpdateAttributes
             ProfileDefinition.UpdateAttributes
-        );
+        ).orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED ) );
 
 
-        if ( !updateProfileID.isPresent() )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_NO_PROFILE_ASSIGNED );
-        }
-
-        return restRequest.getDomain().getConfig().getUpdateAttributesProfile().get( updateProfileID.get() );
+        return restRequest.getDomain().getConfig().getUpdateAttributesProfile().get( updateProfileID );
     }
     }
 
 
     private static RestResultBean doPostProfileDataImpl(
     private static RestResultBean doPostProfileDataImpl(
@@ -203,7 +197,7 @@ public class RestProfileServer extends RestServlet
                 jsonInput.getUsername(),
                 jsonInput.getUsername(),
                 restRequest.readParameterAsString( FIELD_USERNAME ),
                 restRequest.readParameterAsString( FIELD_USERNAME ),
                 FIELD_USERNAME, RestUtility.ReadValueFlag.optional
                 FIELD_USERNAME, RestUtility.ReadValueFlag.optional
-        );
+        ).orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_FIELD_REQUIRED, FIELD_USERNAME ) );
 
 
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
 

+ 3 - 3
server/src/main/java/password/pwm/ws/server/rest/RestSetPasswordServer.java

@@ -111,19 +111,19 @@ public class RestSetPasswordServer extends RestServlet
                             restRequest.readParameterAsString( FIELD_USERNAME ),
                             restRequest.readParameterAsString( FIELD_USERNAME ),
                             FIELD_USERNAME,
                             FIELD_USERNAME,
                             RestUtility.ReadValueFlag.optional
                             RestUtility.ReadValueFlag.optional
-                    ),
+                    ).orElse( null ),
                     RestUtility.readValueFromJsonAndParam(
                     RestUtility.readValueFromJsonAndParam(
                             jsonBody == null ? null : jsonBody.getPassword(),
                             jsonBody == null ? null : jsonBody.getPassword(),
                             restRequest.readParameterAsString( FIELD_PASSWORD ),
                             restRequest.readParameterAsString( FIELD_PASSWORD ),
                             FIELD_PASSWORD,
                             FIELD_PASSWORD,
                             RestUtility.ReadValueFlag.optional
                             RestUtility.ReadValueFlag.optional
-                    ),
+                    ).orElse( null ),
                     Boolean.parseBoolean( RestUtility.readValueFromJsonAndParam(
                     Boolean.parseBoolean( RestUtility.readValueFromJsonAndParam(
                             jsonBody == null ? "" : String.valueOf( jsonBody.isRandom() ),
                             jsonBody == null ? "" : String.valueOf( jsonBody.isRandom() ),
                             restRequest.readParameterAsString( FIELD_RANDOM ),
                             restRequest.readParameterAsString( FIELD_RANDOM ),
                             FIELD_RANDOM,
                             FIELD_RANDOM,
                             RestUtility.ReadValueFlag.optional
                             RestUtility.ReadValueFlag.optional
-                    ) )
+                    ).orElse( null ) )
             );
             );
         }
         }
 
 

+ 2 - 2
server/src/main/java/password/pwm/ws/server/rest/RestVerifyOtpServer.java

@@ -83,12 +83,12 @@ public class RestVerifyOtpServer extends RestServlet
                             jsonBody == null ? null : jsonBody.getToken(),
                             jsonBody == null ? null : jsonBody.getToken(),
                             restRequest.readParameterAsString( "token" ),
                             restRequest.readParameterAsString( "token" ),
                             "token"
                             "token"
-                    ),
+                    ).orElse( null ),
                     RestUtility.readValueFromJsonAndParam(
                     RestUtility.readValueFromJsonAndParam(
                             jsonBody == null ? null : jsonBody.getUsername(),
                             jsonBody == null ? null : jsonBody.getUsername(),
                             restRequest.readParameterAsString( "username" ),
                             restRequest.readParameterAsString( "username" ),
                             "username"
                             "username"
-                    )
+                    ).orElse( null )
             );
             );
         }
         }
 
 

+ 8 - 4
server/src/main/java/password/pwm/ws/server/rest/RestVerifyResponsesServer.java

@@ -25,9 +25,10 @@ import com.novell.ldapchai.cr.Challenge;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.ResponseSet;
 import com.novell.ldapchai.cr.bean.ChallengeBean;
 import com.novell.ldapchai.cr.bean.ChallengeBean;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import lombok.Data;
+import lombok.Value;
 import password.pwm.PwmConstants;
 import password.pwm.PwmConstants;
 import password.pwm.config.option.WebServiceUsage;
 import password.pwm.config.option.WebServiceUsage;
+import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.HttpMethod;
@@ -62,7 +63,9 @@ public class RestVerifyResponsesServer extends RestServlet
 {
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( RestVerifyResponsesServer.class );
     private static final PwmLogger LOGGER = PwmLogger.forClass( RestVerifyResponsesServer.class );
 
 
-    @Data
+    private static final String FIELD_USERNAME = "username";
+
+    @Value
     public static class JsonPutChallengesInput implements Serializable
     public static class JsonPutChallengesInput implements Serializable
     {
     {
         public List<ChallengeBean> challenges;
         public List<ChallengeBean> challenges;
@@ -107,8 +110,9 @@ public class RestVerifyResponsesServer extends RestServlet
 
 
         final String username = RestUtility.readValueFromJsonAndParam(
         final String username = RestUtility.readValueFromJsonAndParam(
                 jsonInput.getUsername(),
                 jsonInput.getUsername(),
-                restRequest.readParameterAsString( "username", PwmHttpRequestWrapper.Flag.BypassValidation ),
-                "username" );
+                restRequest.readParameterAsString( FIELD_USERNAME, PwmHttpRequestWrapper.Flag.BypassValidation ),
+                FIELD_USERNAME
+        ).orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_FIELD_REQUIRED, FIELD_USERNAME ) );
 
 
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
 

+ 4 - 3
server/src/test/java/password/pwm/util/localdb/LocalDBLoggerExtendedTest.java

@@ -50,6 +50,7 @@ import java.time.Instant;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 import java.util.Random;
 import java.util.Random;
 import java.util.Timer;
 import java.util.Timer;
@@ -194,13 +195,13 @@ public class LocalDBLoggerExtendedTest
 
 
     private void outputDebugInfo()
     private void outputDebugInfo()
     {
     {
-        final Map<String, String> debugParams = Map.of(
+        final Map<String, String> debugParams = new HashMap<>( Map.of(
                 "size", StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( localDB.getFileLocation() ) ),
                 "size", StringUtil.formatDiskSize( FileSystemUtility.getFileDirectorySize( localDB.getFileLocation() ) ),
                 "eventsInDb", figureEventsInDbStat(),
                 "eventsInDb", figureEventsInDbStat(),
                 "free", StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( localDB.getFileLocation() ) ),
                 "free", StringUtil.formatDiskSize( FileSystemUtility.diskSpaceRemaining( localDB.getFileLocation() ) ),
                 "eps", eventRateMeter.readEventRate().setScale( 0, RoundingMode.UP ).toString(),
                 "eps", eventRateMeter.readEventRate().setScale( 0, RoundingMode.UP ).toString(),
-                "remain", settings.testDuration.subtract( TimeDuration.fromCurrent( startTime ) ).asCompactString(),
-                "tail", TimeDuration.fromCurrent( localDBLogger.getTailDate() ).asCompactString() );
+                "remain", settings.testDuration.subtract( TimeDuration.fromCurrent( startTime ) ).asCompactString() ) );
+        localDBLogger.getTailDate().ifPresent( tailDate -> debugParams.put( "tail", TimeDuration.compactFromCurrent( tailDate ) ) );
         out( "added " + StringUtil.mapToString( debugParams ) );
         out( "added " + StringUtil.mapToString( debugParams ) );
     }
     }
 
 

+ 2 - 1
webapp/src/main/webapp/WEB-INF/jsp/admin-logview.jsp

@@ -25,6 +25,7 @@
 
 
 <%@ page import="password.pwm.i18n.Admin" %>
 <%@ page import="password.pwm.i18n.Admin" %>
 <%@ page import="password.pwm.util.logging.LocalDBLogger" %>
 <%@ page import="password.pwm.util.logging.LocalDBLogger" %>
+<%@ page import="java.time.Instant" %>
 <!DOCTYPE html>
 <!DOCTYPE html>
 <%@ page language="java" session="true" isThreadSafe="true"
 <%@ page language="java" session="true" isThreadSafe="true"
          contentType="text/html" %>
          contentType="text/html" %>
@@ -110,7 +111,7 @@
                     <p>
                     <p>
                         This page shows the debug log
                         This page shows the debug log
                         history. This records shown here are stored in the LocalDB.  The LocalDB contains <%=JspUtility.friendlyWrite( pageContext, localDBLogger.getStoredEventCount() )%> events. The oldest event stored in the LocalDB is from
                         history. This records shown here are stored in the LocalDB.  The LocalDB contains <%=JspUtility.friendlyWrite( pageContext, localDBLogger.getStoredEventCount() )%> events. The oldest event stored in the LocalDB is from
-                        <span class="timestamp"><%= JspUtility.friendlyWrite( pageContext, JspUtility.getPwmRequest( pageContext ).getPwmApplication().getLocalDBLogger().getTailDate() ) %></span>.
+                        <span class="timestamp"><%= JspUtility.friendlyWrite( pageContext, JspUtility.getPwmRequest( pageContext ).getPwmApplication().getLocalDBLogger().getTailDate().orElse( Instant.EPOCH ) ) %></span>.
                     </p>
                     </p>
                     <p>
                     <p>
                         The LocalDB is configured to capture events of level
                         The LocalDB is configured to capture events of level