Sfoglia il codice sorgente

user guid reader improvements and correctness

Jason Rivard 2 anni fa
parent
commit
b3d858ce90
23 ha cambiato i file con 416 aggiunte e 399 eliminazioni
  1. 0 7
      server/src/main/java/password/pwm/PwmApplication.java
  2. 1 1
      server/src/main/java/password/pwm/PwmDomain.java
  3. 0 14
      server/src/main/java/password/pwm/config/profile/AbstractProfile.java
  4. 13 0
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  5. 5 0
      server/src/main/java/password/pwm/error/PwmUnrecoverableException.java
  6. 1 4
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordServlet.java
  7. 15 4
      server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java
  8. 2 9
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java
  9. 15 23
      server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java
  10. 246 0
      server/src/main/java/password/pwm/ldap/LdapGuidReaderUtil.java
  11. 22 219
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  12. 1 1
      server/src/main/java/password/pwm/ldap/LdapUserInfoReader.java
  13. 1 1
      server/src/main/java/password/pwm/svc/PwmServiceEnum.java
  14. 29 30
      server/src/main/java/password/pwm/svc/cr/CrService.java
  15. 24 22
      server/src/main/java/password/pwm/svc/email/EmailServerUtil.java
  16. 20 15
      server/src/main/java/password/pwm/svc/otp/OtpService.java
  17. 5 13
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java
  18. 2 1
      server/src/main/java/password/pwm/svc/userhistory/DatabaseUserHistory.java
  19. 1 3
      server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java
  20. 6 5
      server/src/main/java/password/pwm/util/debug/CacheServiceDebugItemGenerator.java
  21. 1 1
      server/src/main/java/password/pwm/util/debug/DebugItemGenerator.java
  22. 1 2
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  23. 5 24
      server/src/main/java/password/pwm/ws/server/rest/RestChallengesServer.java

+ 0 - 7
server/src/main/java/password/pwm/PwmApplication.java

@@ -36,7 +36,6 @@ import password.pwm.http.state.SessionStateService;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.PwmServiceEnum;
 import password.pwm.svc.PwmServiceManager;
-import password.pwm.svc.cache.CacheService;
 import password.pwm.svc.db.DatabaseAccessor;
 import password.pwm.svc.db.DatabaseService;
 import password.pwm.svc.email.EmailService;
@@ -745,12 +744,6 @@ public class PwmApplication
         return ( SessionStateService ) pwmServiceManager.getService( PwmServiceEnum.SessionStateSvc );
     }
 
-
-    public CacheService getCacheService( )
-    {
-        return ( CacheService ) pwmServiceManager.getService( PwmServiceEnum.CacheService );
-    }
-
     public SystemSecureService getSecureService( )
     {
         return ( SystemSecureService ) pwmServiceManager.getService( PwmServiceEnum.SystemSecureService );

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

@@ -141,7 +141,7 @@ public class PwmDomain
 
     public CacheService getCacheService( )
     {
-        return pwmApplication.getCacheService();
+        return ( CacheService ) pwmServiceManager.getService( PwmServiceEnum.CacheService );
     }
 
     public DomainSecureService getSecureService( )

+ 0 - 14
server/src/main/java/password/pwm/config/profile/AbstractProfile.java

@@ -31,7 +31,6 @@ import password.pwm.config.value.data.ActionConfiguration;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.util.PasswordData;
-import password.pwm.util.java.EnumUtil;
 
 import java.security.cert.X509Certificate;
 import java.util.Collections;
@@ -48,13 +47,6 @@ public abstract class AbstractProfile implements Profile
     private final StoredConfiguration storedConfiguration;
     private final StoredSettingReader settingReader;
 
-    public enum GuidMode
-    {
-        DN,
-        VENDORGUID,
-        ATTRIBUTE,
-    }
-
     AbstractProfile( final DomainID domainID, final ProfileID profileID, final StoredConfiguration storedConfiguration )
     {
         this.profileID = Objects.requireNonNull( profileID );
@@ -172,10 +164,4 @@ public abstract class AbstractProfile implements Profile
         }
         return Collections.unmodifiableSet( result );
     }
-
-    public GuidMode readGuidMode()
-    {
-        final String guidAttributeName = readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
-        return EnumUtil.readEnumFromString( GuidMode.class, guidAttributeName ).orElse( GuidMode.VENDORGUID );
-    }
 }

+ 13 - 0
server/src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -38,6 +38,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
+import password.pwm.util.java.EnumUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -62,6 +63,12 @@ public class LdapProfile extends AbstractProfile implements Profile
     private Optional<UserIdentity> cachedTestUser;
     private UserIdentity cachedProxyUser;
 
+    public enum GuidMode
+    {
+        DN,
+        VENDORGUID,
+        ATTRIBUTE,
+    }
 
     protected LdapProfile( final DomainID domainID, final ProfileID identifier, final StoredConfiguration storedValueMap )
     {
@@ -307,4 +314,10 @@ public class LdapProfile extends AbstractProfile implements Profile
         final String msg = "specified search context '" + testDN + "' is not contained by a configured root context";
         throw new PwmUnrecoverableException( PwmError.CONFIG_FORMAT_ERROR, msg );
     }
+
+    public GuidMode getGuidMode()
+    {
+        final String guidAttributeString = readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
+        return EnumUtil.readEnumFromString( GuidMode.class, guidAttributeString ).orElse( GuidMode.ATTRIBUTE );
+    }
 }

+ 5 - 0
server/src/main/java/password/pwm/error/PwmUnrecoverableException.java

@@ -72,6 +72,11 @@ public class PwmUnrecoverableException extends PwmException
         return new PwmUnrecoverableException( new ErrorInformation( error, message ) );
     }
 
+    public static PwmUnrecoverableException newException( final PwmError error )
+    {
+        return new PwmUnrecoverableException( new ErrorInformation( error ) );
+    }
+
     public static PwmUnrecoverableException convert( final IOException e )
     {
         final String msg = "unexpected io error: " + e.getMessage();

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

@@ -63,7 +63,6 @@ import password.pwm.http.servlet.oauth.OAuthForgottenPasswordResults;
 import password.pwm.http.servlet.oauth.OAuthMachine;
 import password.pwm.http.servlet.oauth.OAuthSettings;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.AuthenticationUtility;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
@@ -974,7 +973,6 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
     protected void nextStep( final PwmRequest pwmRequest )
             throws IOException, ServletException, PwmUnrecoverableException, ChaiUnavailableException
     {
-        final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final DomainConfig config = pwmRequest.getDomainConfig();
         final ForgottenPasswordBean forgottenPasswordBean = forgottenPasswordBean( pwmRequest );
 
@@ -1018,8 +1016,7 @@ public class ForgottenPasswordServlet extends ControlledPwmServlet
             if ( !progress.getSatisfiedMethods().contains( IdentityVerificationMethod.PREVIOUS_AUTH ) )
             {
                 final UserIdentity userIdentity = forgottenPasswordBean.getUserIdentity();
-                final String userGuid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, pwmRequest.getLabel(), userIdentity, true );
-                if ( ForgottenPasswordUtil.checkAuthRecord( pwmRequest, userGuid ) )
+                if ( ForgottenPasswordUtil.checkAuthRecord( pwmRequest, userIdentity ) )
                 {
                     LOGGER.debug( pwmRequest, () -> "marking " + IdentityVerificationMethod.PREVIOUS_AUTH + " method as satisfied" );
                     progress.getSatisfiedMethods().add( IdentityVerificationMethod.PREVIOUS_AUTH );

+ 15 - 4
server/src/main/java/password/pwm/http/servlet/forgottenpw/ForgottenPasswordUtil.java

@@ -60,6 +60,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.auth.HttpAuthRecord;
 import password.pwm.http.bean.ForgottenPasswordBean;
 import password.pwm.i18n.Message;
+import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecord;
@@ -209,16 +210,26 @@ public class ForgottenPasswordUtil
         );
     }
 
-    static boolean checkAuthRecord( final PwmRequest pwmRequest, final String userGuid )
+    static boolean checkAuthRecord(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
     {
-        if ( userGuid == null || userGuid.isEmpty() )
+        final PwmDomain pwmDomain = pwmRequest.getPwmApplication().domains().get( userIdentity.getDomainID() );
+        final Optional<String> userGuid = LdapOperationsHelper.readLdapGuidValue(
+                pwmDomain,
+                pwmRequest.getLabel(),
+                userIdentity );
+
+        if ( userGuid.isEmpty() )
         {
             return false;
         }
 
         try
         {
-            final String cookieName = pwmRequest.getDomainConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
+            final String cookieName = pwmDomain.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_AUTHRECORD_NAME );
             if ( cookieName == null || cookieName.isEmpty() )
             {
                 LOGGER.trace( pwmRequest, () -> "skipping auth record cookie read, cookie name parameter is blank" );
@@ -229,7 +240,7 @@ public class ForgottenPasswordUtil
             if ( optionalHttpAuthRecord.isPresent() )
             {
                 final HttpAuthRecord httpAuthRecord = optionalHttpAuthRecord.get();
-                if ( httpAuthRecord.getGuid() != null && !httpAuthRecord.getGuid().isEmpty() && httpAuthRecord.getGuid().equals( userGuid ) )
+                if ( userGuid.get().equals( httpAuthRecord.getGuid() ) )
                 {
                     LOGGER.debug( pwmRequest, () -> "auth record cookie validated" );
                     return true;

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

@@ -61,7 +61,6 @@ import password.pwm.http.servlet.ControlledPwmServlet;
 import password.pwm.http.servlet.peoplesearch.PhotoDataReader;
 import password.pwm.http.servlet.peoplesearch.SearchRequestBean;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchResults;
@@ -83,8 +82,8 @@ import password.pwm.user.UserInfo;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.CollectionUtil;
 import password.pwm.util.java.JavaHelper;
-import password.pwm.util.java.PwmUtil;
 import password.pwm.util.java.PwmTimeUtil;
+import password.pwm.util.java.PwmUtil;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.json.JsonFactory;
@@ -1162,18 +1161,12 @@ public class HelpdeskServlet extends ControlledPwmServlet
         }
 
         final ChaiUser chaiUser = HelpdeskServletUtil.getChaiUser( pwmRequest, helpdeskProfile, userIdentity );
-        final String userGUID = LdapOperationsHelper.readLdapGuidValue(
-                pwmRequest.getPwmDomain(),
-                pwmRequest.getLabel(),
-                userIdentity,
-                true );
 
         final CrService crService = pwmRequest.getPwmDomain().getCrService();
         crService.clearResponses(
                 pwmRequest.getLabel(),
                 userIdentity,
-                chaiUser,
-                userGUID
+                chaiUser
         );
 
         // mark the event log

+ 15 - 23
server/src/main/java/password/pwm/http/servlet/setupresponses/SetupResponsesServlet.java

@@ -235,30 +235,23 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         LOGGER.trace( pwmRequest, () -> "request for response clear received" );
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
-        try
-        {
-            final String userGUID = pwmSession.getUserInfo().getUserGuid();
-            final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
-            pwmDomain.getCrService().clearResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, userGUID );
-            pwmSession.reloadUserInfoBean( pwmRequest );
-            pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
 
-            // mark the event log
-            final UserAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
-                    AuditEvent.CLEAR_RESPONSES,
-                    pwmSession.getUserInfo(),
-                    pwmSession
-            );
+        final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
+        pwmDomain.getCrService().clearResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser );
+        pwmSession.reloadUserInfoBean( pwmRequest );
+        pwmRequest.getPwmDomain().getSessionStateService().clearBean( pwmRequest, SetupResponsesBean.class );
 
-            AuditServiceClient.submit( pwmRequest, auditRecord );
+        // mark the event log
+        final UserAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createUserAuditRecord(
+                AuditEvent.CLEAR_RESPONSES,
+                pwmSession.getUserInfo(),
+                pwmSession
+        );
+
+        AuditServiceClient.submit( pwmRequest, auditRecord );
+
+        pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.SetupResponses );
 
-            pwmRequest.getPwmResponse().sendRedirect( PwmServletDefinition.SetupResponses );
-        }
-        catch ( final PwmOperationalException e )
-        {
-            LOGGER.debug( pwmRequest, e.getErrorInformation() );
-            setLastError( pwmRequest, e.getErrorInformation() );
-        }
         return ProcessStatus.Continue;
     }
 
@@ -453,8 +446,7 @@ public class SetupResponsesServlet extends ControlledPwmServlet
         final PwmDomain pwmDomain = pwmRequest.getPwmDomain();
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final ChaiUser theUser = pwmRequest.getClientConnectionHolder().getActor( );
-        final String userGUID = pwmSession.getUserInfo().getUserGuid();
-        pwmDomain.getCrService().writeResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, userGUID, responseInfoBean );
+        pwmDomain.getCrService().writeResponses( pwmRequest.getLabel(), pwmRequest.getUserInfoIfLoggedIn(), theUser, responseInfoBean );
         pwmSession.reloadUserInfoBean( pwmRequest );
 
         StatisticsClient.incrementStat( pwmRequest, Statistic.SETUP_RESPONSES );

+ 246 - 0
server/src/main/java/password/pwm/ldap/LdapGuidReaderUtil.java

@@ -0,0 +1,246 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2021 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap;
+
+import com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import password.pwm.AppProperty;
+import password.pwm.PwmDomain;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.ldap.search.UserSearchService;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroRequest;
+
+import java.util.Optional;
+
+class LdapGuidReaderUtil
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( LdapGuidReaderUtil.class );
+
+    static Optional<String> readExistingGuidValue(
+            final PwmDomain pwmDomain,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity );
+        final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() );
+        final LdapProfile.GuidMode guidMode = ldapProfile.getGuidMode();
+
+        if ( guidMode == LdapProfile.GuidMode.DN )
+        {
+            return Optional.of( userIdentity.getUserDN() );
+        }
+
+        if ( guidMode == LdapProfile.GuidMode.VENDORGUID )
+        {
+            return readVendorGuid( theUser, sessionLabel );
+        }
+
+        return readAttributeGuid( sessionLabel, ldapProfile, userIdentity, theUser );
+    }
+
+    private static Optional<String> readAttributeGuid(
+            final SessionLabel sessionLabel,
+            final LdapProfile ldapProfile,
+            final UserIdentity userIdentity,
+            final ChaiUser theUser
+    )
+            throws PwmUnrecoverableException
+    {
+        final String guidAttributeName = ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
+        try
+        {
+            final String value = theUser.readStringAttribute( guidAttributeName );
+            if ( StringUtil.isEmpty( value ) )
+            {
+                LOGGER.warn( sessionLabel, () -> "missing GUID value for user " + userIdentity
+                        + " from '" + guidAttributeName + "' attribute" );
+            }
+            else
+            {
+                LOGGER.trace( sessionLabel, () -> "read GUID value for user " + userIdentity
+                        + " using attribute '"
+                        + guidAttributeName + "': " + value );
+                return Optional.of( value );
+            }
+        }
+        catch ( final ChaiOperationException e )
+        {
+            LOGGER.warn( sessionLabel, () -> "unexpected error while reading attribute GUID "
+                    + "value for user "
+                    + userIdentity + " from '" + guidAttributeName + "' attribute, error: " + e.getMessage() );
+        }
+        catch ( final ChaiUnavailableException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
+
+        return Optional.empty();
+    }
+
+    private static Optional<String> readVendorGuid(
+            final ChaiUser theUser,
+            final SessionLabel sessionLabel
+    )
+    {
+        try
+        {
+            final String guidValue = theUser.readGUID();
+            if ( StringUtil.isEmpty( guidValue ) )
+            {
+                LOGGER.warn( sessionLabel,
+                        () -> "unable to find a VENDORGUID value for user " + theUser.getEntryDN() );
+            }
+            else
+            {
+                LOGGER.trace( sessionLabel, () -> "read VENDORGUID value for user " + theUser
+                        + ": " + guidValue );
+                return Optional.of( guidValue );
+            }
+        }
+        catch ( final Exception e )
+        {
+            LOGGER.warn( sessionLabel, () -> "error while reading vendor GUID value for user "
+                    + theUser.getEntryDN() + ", error: " + e.getMessage() );
+        }
+
+        return Optional.empty();
+    }
+
+
+    private static boolean searchForExistingGuidValue(
+            final PwmDomain pwmDomain,
+            final SessionLabel sessionLabel,
+            final String guidValue
+    )
+            throws PwmUnrecoverableException
+    {
+        boolean exists = false;
+        for ( final LdapProfile ldapProfile : pwmDomain.getConfig().getLdapProfiles().values() )
+        {
+            final LdapProfile.GuidMode guidMode = ldapProfile.getGuidMode();
+            if ( guidMode == LdapProfile.GuidMode.ATTRIBUTE )
+            {
+                try
+                {
+                    final String guidAttributeName = ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
+
+                    // check if it is unique
+                    final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
+                            .filter( "(" + guidAttributeName + "=" + guidValue + ")" )
+                            .build();
+
+                    final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
+                    final UserIdentity result = userSearchService.performSingleUserSearch( searchConfiguration,
+                            sessionLabel );
+                    exists = result != null;
+                }
+                catch ( final PwmOperationalException e )
+                {
+                    if ( e.getError() != PwmError.ERROR_CANT_MATCH_USER )
+                    {
+                        LOGGER.warn( sessionLabel, () -> "error while searching to verify new "
+                                + "unique GUID value: " + e.getError() );
+                    }
+                }
+            }
+        }
+        return exists;
+    }
+
+    static String assignGuidToUser(
+            final PwmDomain pwmDomain,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final String guidAttributeName
+    )
+            throws PwmUnrecoverableException
+    {
+        int attempts = 0;
+        String newGuid = null;
+
+        while ( attempts < 10 && newGuid == null )
+        {
+            attempts++;
+            newGuid = generateGuidValue( pwmDomain, sessionLabel );
+            if ( searchForExistingGuidValue( pwmDomain, sessionLabel, newGuid ) )
+            {
+                newGuid = null;
+            }
+        }
+
+        if ( newGuid == null )
+        {
+            throw new PwmUnrecoverableException( new ErrorInformation(
+                    PwmError.ERROR_INTERNAL,
+                    "unable to generate unique GUID value for user " + userIdentity )
+            );
+        }
+
+        LdapOperationsHelper.addConfiguredUserObjectClass( sessionLabel, userIdentity, pwmDomain );
+        try
+        {
+            // write it to the directory
+            final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity );
+            chaiUser.writeStringAttribute( guidAttributeName, newGuid );
+            final String finalNewGuid = newGuid;
+            LOGGER.debug( sessionLabel,
+                    () -> "added GUID value '" + finalNewGuid + "' to user " + userIdentity );
+            return newGuid;
+        }
+        catch ( final ChaiOperationException e )
+        {
+            final String errorMsg =
+                    "unable to write GUID value to user attribute " + guidAttributeName + " : " + e.getMessage()
+                    + ", cannot write GUID value to user " + userIdentity;
+            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg );
+            LOGGER.warn( errorInformation::toDebugStr );
+            throw new PwmUnrecoverableException( errorInformation );
+        }
+        catch ( final ChaiUnavailableException e )
+        {
+            throw PwmUnrecoverableException.fromChaiException( e );
+        }
+    }
+
+    private static String generateGuidValue(
+            final PwmDomain pwmDomain,
+            final SessionLabel sessionLabel
+    )
+    {
+        final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmDomain.getPwmApplication(),
+                sessionLabel );
+        final String guidPattern = pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_GUID_PATTERN );
+        return macroRequest.expandMacros( guidPattern );
+    }
+}

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

@@ -43,7 +43,6 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.DomainConfig;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.option.AutoSetLdapUserLanguage;
-import password.pwm.config.profile.AbstractProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.data.ImmutableByteArray;
@@ -51,8 +50,6 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchService;
 import password.pwm.svc.cache.CacheKey;
 import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.EpsStatistic;
@@ -85,6 +82,8 @@ public class LdapOperationsHelper
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( LdapOperationsHelper.class );
 
+    private static final String NULL_CACHE_GUID = "NULL_CACHE_GUID";
+
     public static void addConfiguredUserObjectClass(
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
@@ -203,14 +202,10 @@ public class LdapOperationsHelper
         }
     }
 
-
-    private static final String NULL_CACHE_GUID = "NULL_CACHE_GUID";
-
-    public static String readLdapGuidValue(
+    public static Optional<String> readLdapGuidValue(
             final PwmDomain pwmDomain,
             final SessionLabel sessionLabel,
-            final UserIdentity userIdentity,
-            final boolean throwExceptionOnError
+            final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
     {
@@ -223,41 +218,42 @@ public class LdapOperationsHelper
             if ( cachedValue != null )
             {
                 return NULL_CACHE_GUID.equals( cachedValue )
-                        ? null
-                        : cachedValue;
+                        ? Optional.empty()
+                        : Optional.of( cachedValue );
             }
         }
 
-        final String existingValue = GuidReaderUtil.readExistingGuidValue(
+        final Optional<String> existingValue = LdapGuidReaderUtil.readExistingGuidValue(
                 pwmDomain,
                 sessionLabel,
-                userIdentity,
-                throwExceptionOnError
-        );
+                userIdentity );
 
-        final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() );
-        final String guidAttributeName = ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
-        if ( StringUtil.isEmpty( existingValue ) )
+        if ( existingValue.isEmpty() )
         {
-            if ( !"DN".equalsIgnoreCase( guidAttributeName ) && !"VENDORGUID".equalsIgnoreCase( guidAttributeName ) )
+            final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() );
+            final LdapProfile.GuidMode guidMode = ldapProfile.getGuidMode();
+
+            if ( guidMode == LdapProfile.GuidMode.ATTRIBUTE )
             {
                 if ( ldapProfile.readSettingAsBoolean( PwmSetting.LDAP_GUID_AUTO_ADD ) )
                 {
-                    LOGGER.trace( sessionLabel, () -> "assigning new GUID to user " + userIdentity );
-                    return GuidReaderUtil.assignGuidToUser( pwmDomain, sessionLabel, userIdentity, guidAttributeName );
+                    LOGGER.trace( sessionLabel, () -> "auto-assigning new GUID to user " + userIdentity );
+                    final String newGuid = LdapGuidReaderUtil.assignGuidToUser(
+                            pwmDomain,
+                            sessionLabel,
+                            userIdentity,
+                            ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE ) );
+
+                    return Optional.of( newGuid );
                 }
             }
-            final String errorMsg = "unable to resolve GUID value for user " + userIdentity;
-            GuidReaderUtil.processError( errorMsg, throwExceptionOnError );
         }
 
         if ( enableCache )
         {
             final long cacheSeconds = Long.parseLong( pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_CACHE_USER_GUID_SECONDS ) );
             final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration( TimeDuration.of( cacheSeconds, TimeDuration.Unit.SECONDS ) );
-            final String cacheValue = existingValue == null
-                    ? NULL_CACHE_GUID
-                    : existingValue;
+            final String cacheValue = existingValue.orElse( NULL_CACHE_GUID );
             pwmDomain.getCacheService().put( cacheKey, cachePolicy, cacheValue );
         }
 
@@ -456,199 +452,6 @@ public class LdapOperationsHelper
         }
     }
 
-
-    private static class GuidReaderUtil
-    {
-        private static String readExistingGuidValue(
-                final PwmDomain pwmDomain,
-                final SessionLabel sessionLabel,
-                final UserIdentity userIdentity,
-                final boolean throwExceptionOnError
-        )
-                throws PwmUnrecoverableException
-        {
-            final ChaiUser theUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity );
-            final LdapProfile ldapProfile = pwmDomain.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() );
-            final AbstractProfile.GuidMode guidMode = ldapProfile.readGuidMode();
-
-            if ( guidMode == AbstractProfile.GuidMode.DN )
-            {
-                return userIdentity.getUserDN();
-            }
-
-            if ( guidMode == AbstractProfile.GuidMode.VENDORGUID )
-            {
-                return readVendorGuid( theUser, sessionLabel, throwExceptionOnError );
-            }
-
-            return readAttributeGuid( ldapProfile, userIdentity, theUser, throwExceptionOnError );
-        }
-
-        private static String readAttributeGuid(
-                final LdapProfile ldapProfile,
-                final UserIdentity userIdentity,
-                final ChaiUser theUser,
-                final boolean throwExceptionOnError
-        )
-                throws PwmUnrecoverableException
-        {
-            final String guidAttributeName = ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
-            try
-            {
-                return theUser.readStringAttribute( guidAttributeName );
-            }
-            catch ( final ChaiOperationException e )
-            {
-                final String errorMsg = "unexpected error while reading attribute GUID value for user "
-                        + userIdentity + " from '" + guidAttributeName + "', error: " + e.getMessage();
-                return processError( errorMsg, throwExceptionOnError );
-            }
-            catch ( final ChaiUnavailableException e )
-            {
-                throw PwmUnrecoverableException.fromChaiException( e );
-            }
-        }
-
-        private static String readVendorGuid(
-                final ChaiUser theUser,
-                final SessionLabel sessionLabel,
-                final boolean throwExceptionOnError
-        )
-                throws PwmUnrecoverableException
-        {
-            try
-            {
-                final String guidValue = theUser.readGUID();
-                if ( guidValue != null && guidValue.length() > 1 )
-                {
-                    LOGGER.trace( sessionLabel, () -> "read VENDORGUID value for user " + theUser + ": " + guidValue );
-                }
-                else
-                {
-                    LOGGER.trace( sessionLabel, () -> "unable to find a VENDORGUID value for user " + theUser.getEntryDN() );
-                }
-                return guidValue;
-            }
-            catch ( final Exception e )
-            {
-                final String errorMsg = "error while reading vendor GUID value for user " + theUser.getEntryDN() + ", error: " + e.getMessage();
-                return processError( errorMsg, throwExceptionOnError );
-            }
-        }
-
-        private static String processError( final String errorMsg, final boolean throwExceptionOnError )
-                throws PwmUnrecoverableException
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_MISSING_GUID, errorMsg );
-            if ( throwExceptionOnError )
-            {
-                throw new PwmUnrecoverableException( errorInformation );
-            }
-            LOGGER.warn( () -> errorMsg );
-            return null;
-        }
-
-        private static boolean searchForExistingGuidValue(
-                final PwmDomain pwmDomain,
-                final SessionLabel sessionLabel,
-                final String guidValue
-        )
-                throws PwmUnrecoverableException
-        {
-            boolean exists = false;
-            for ( final LdapProfile ldapProfile : pwmDomain.getConfig().getLdapProfiles().values() )
-            {
-                final String guidAttributeName = ldapProfile.readSettingAsString( PwmSetting.LDAP_GUID_ATTRIBUTE );
-                if ( !"DN".equalsIgnoreCase( guidAttributeName ) && !"VENDORGUID".equalsIgnoreCase( guidAttributeName ) )
-                {
-                    try
-                    {
-                        // check if it is unique
-                        final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
-                                .filter( "(" + guidAttributeName + "=" + guidValue + ")" )
-                                .build();
-
-                        final UserSearchService userSearchService = pwmDomain.getUserSearchEngine();
-                        final UserIdentity result = userSearchService.performSingleUserSearch( searchConfiguration, sessionLabel );
-                        exists = result != null;
-                    }
-                    catch ( final PwmOperationalException e )
-                    {
-                        if ( e.getError() != PwmError.ERROR_CANT_MATCH_USER )
-                        {
-                            LOGGER.warn( sessionLabel, () -> "error while searching to verify new unique GUID value: " + e.getError() );
-                        }
-                    }
-                }
-            }
-            return exists;
-        }
-
-        private static String assignGuidToUser(
-                final PwmDomain pwmDomain,
-                final SessionLabel sessionLabel,
-                final UserIdentity userIdentity,
-                final String guidAttributeName
-        )
-                throws PwmUnrecoverableException
-        {
-            int attempts = 0;
-            String newGuid = null;
-
-            while ( attempts < 10 && newGuid == null )
-            {
-                attempts++;
-                newGuid = generateGuidValue( pwmDomain, sessionLabel );
-                if ( searchForExistingGuidValue( pwmDomain, sessionLabel, newGuid ) )
-                {
-                    newGuid = null;
-                }
-            }
-
-            if ( newGuid == null )
-            {
-                throw new PwmUnrecoverableException( new ErrorInformation(
-                        PwmError.ERROR_INTERNAL,
-                        "unable to generate unique GUID value for user " + userIdentity )
-                );
-            }
-
-            addConfiguredUserObjectClass( sessionLabel, userIdentity, pwmDomain );
-            try
-            {
-                // write it to the directory
-                final ChaiUser chaiUser = pwmDomain.getProxiedChaiUser( sessionLabel, userIdentity );
-                chaiUser.writeStringAttribute( guidAttributeName, newGuid );
-                final String finalNewGuid = newGuid;
-                LOGGER.info( sessionLabel, () -> "added GUID value '" + finalNewGuid + "' to user " + userIdentity );
-                return newGuid;
-            }
-            catch ( final ChaiOperationException e )
-            {
-                final String errorMsg = "unable to write GUID value to user attribute " + guidAttributeName + " : " + e.getMessage()
-                        + ", cannot write GUID value to user " + userIdentity;
-                final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, errorMsg );
-                LOGGER.error( errorInformation::toDebugStr );
-                throw new PwmUnrecoverableException( errorInformation );
-            }
-            catch ( final ChaiUnavailableException e )
-            {
-                throw PwmUnrecoverableException.fromChaiException( e );
-            }
-        }
-
-        private static String generateGuidValue(
-                final PwmDomain pwmDomain,
-                final SessionLabel sessionLabel
-        )
-                throws PwmUnrecoverableException
-        {
-            final MacroRequest macroRequest = MacroRequest.forNonUserSpecific( pwmDomain.getPwmApplication(), sessionLabel );
-            final String guidPattern = pwmDomain.getConfig().readAppProperty( AppProperty.LDAP_GUID_PATTERN );
-            return macroRequest.expandMacros( guidPattern );
-        }
-    }
-
     public static ChaiProvider createChaiProvider(
             final PwmDomain pwmDomain,
             final SessionLabel sessionLabel,

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

@@ -597,7 +597,7 @@ public class LdapUserInfoReader implements UserInfo
     @Override
     public String getUserGuid( ) throws PwmUnrecoverableException
     {
-        return LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, false );
+        return LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity ).orElse( null );
     }
 
     @Override

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

@@ -55,7 +55,6 @@ public enum PwmServiceEnum
     IntruderSystemService( IntruderSystemService.class, PwmSettingScope.SYSTEM ),
     SmsQueueManager( SmsQueueService.class, PwmSettingScope.SYSTEM ),
     UrlShortenerService( password.pwm.svc.shorturl.UrlShortenerService.class, PwmSettingScope.SYSTEM ),
-    CacheService( password.pwm.svc.cache.CacheService.class, PwmSettingScope.SYSTEM, Flag.StartDuringRuntimeInstance ),
     LdapSystemService( password.pwm.ldap.LdapSystemService.class, PwmSettingScope.SYSTEM, Flag.StartDuringRuntimeInstance ),
     TokenSystemService( password.pwm.svc.token.TokenSystemService.class, PwmSettingScope.SYSTEM ),
     HealthMonitor( HealthService.class, PwmSettingScope.SYSTEM ),
@@ -67,6 +66,7 @@ public enum PwmServiceEnum
     NodeService( NodeService.class, PwmSettingScope.SYSTEM ),
 
     DomainSecureService( password.pwm.svc.secure.DomainSecureService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
+    CacheService( password.pwm.svc.cache.CacheService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     LdapConnectionService( LdapDomainService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     CrService( password.pwm.svc.cr.CrService.class, PwmSettingScope.DOMAIN, Flag.StartDuringRuntimeInstance ),
     OtpService( password.pwm.svc.otp.OtpService.class, PwmSettingScope.DOMAIN ),

+ 29 - 30
server/src/main/java/password/pwm/svc/cr/CrService.java

@@ -52,7 +52,6 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmDataValidationException;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
-import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.ldap.LdapOperationsHelper;
@@ -444,15 +443,7 @@ public class CrService extends AbstractPwmService implements PwmService
                 + JsonFactory.get().serializeCollection( readPreferences ) + " for response info for user " + theUser.getEntryDN();
         LOGGER.debug( sessionLabel, () -> debugMsg );
 
-        final String userGUID;
-        if ( readPreferences.contains( DataStorageMethod.DB ) || readPreferences.contains( DataStorageMethod.LOCALDB ) )
-        {
-            userGUID = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, false );
-        }
-        else
-        {
-            userGUID = null;
-        }
+        final String userGUID = readUserGuidIfNeeded( userIdentity, sessionLabel, readPreferences );
 
         for ( final DataStorageMethod storageMethod : readPreferences )
         {
@@ -475,6 +466,21 @@ public class CrService extends AbstractPwmService implements PwmService
         return Optional.empty();
     }
 
+    private String readUserGuidIfNeeded(
+            final UserIdentity userIdentity,
+            final SessionLabel sessionLabel,
+            final List<DataStorageMethod> storageMethods
+    )
+            throws PwmUnrecoverableException
+    {
+        /* only some storage methods require user guid, for others we can safely have a null */
+        if ( storageMethods.contains( DataStorageMethod.DB ) || storageMethods.contains( DataStorageMethod.LOCALDB ) )
+        {
+            return LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_GUID ) );
+        }
+        return null;
+    }
 
     public Optional<ResponseSet> readUserResponseSet(
             final SessionLabel sessionLabel,
@@ -492,15 +498,7 @@ public class CrService extends AbstractPwmService implements PwmService
         LOGGER.debug( sessionLabel, () -> "will attempt to read the following storage methods: "
                 + JsonFactory.get().serializeCollection( readPreferences ) + " for user " + theUser.getEntryDN() );
 
-        final String userGUID;
-        if ( readPreferences.contains( DataStorageMethod.DB ) || readPreferences.contains( DataStorageMethod.LOCALDB ) )
-        {
-            userGUID = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, false );
-        }
-        else
-        {
-            userGUID = null;
-        }
+        final String userGUID = readUserGuidIfNeeded( userIdentity, sessionLabel, readPreferences );
 
         for ( final DataStorageMethod storageMethod : readPreferences )
         {
@@ -528,10 +526,9 @@ public class CrService extends AbstractPwmService implements PwmService
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
             final ChaiUser theUser,
-            final String userGUID,
             final ResponseInfoBean responseInfoBean
     )
-            throws PwmOperationalException, ChaiUnavailableException, ChaiValidationException
+            throws PwmUnrecoverableException
     {
         int attempts = 0;
         int successes = 0;
@@ -542,6 +539,7 @@ public class CrService extends AbstractPwmService implements PwmService
         LOGGER.debug( sessionLabel, () -> "will attempt to write the following storage methods: "
                 + JsonFactory.get().serializeCollection( writeMethods ) + " for user " + theUser.getEntryDN() );
 
+        final String userGUID = readUserGuidIfNeeded( userIdentity, sessionLabel, writeMethods );
 
         for ( final DataStorageMethod loopWriteMethod : writeMethods )
         {
@@ -565,7 +563,7 @@ public class CrService extends AbstractPwmService implements PwmService
         {
             final String errorMsg = "no response save methods are available or configured";
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, errorMsg );
-            throw new PwmOperationalException( errorInfo );
+            throw new PwmUnrecoverableException( errorInfo );
         }
 
         if ( successes == 0 )
@@ -573,7 +571,7 @@ public class CrService extends AbstractPwmService implements PwmService
             final String errorMsg = "response storage unsuccessful; attempts=" + attempts + ", successes=" + successes
                     + ", detail=" + JsonFactory.get().serializeMap( errorMessages, DataStorageMethod.class, String.class );
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, errorMsg );
-            throw new PwmOperationalException( errorInfo );
+            throw new PwmUnrecoverableException( errorInfo );
         }
 
         if ( attempts != successes )
@@ -581,7 +579,7 @@ public class CrService extends AbstractPwmService implements PwmService
             final String errorMsg = "response storage only partially successful; attempts=" + attempts + ", successes=" + successes
                     + ", detail=" + JsonFactory.get().serializeMap( errorMessages, DataStorageMethod.class, String.class );
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_WRITING_RESPONSES, errorMsg );
-            throw new PwmOperationalException( errorInfo );
+            throw new PwmUnrecoverableException( errorInfo );
         }
     }
 
@@ -589,11 +587,10 @@ public class CrService extends AbstractPwmService implements PwmService
     public void clearResponses(
             final SessionLabel sessionLabel,
             final UserIdentity userIdentity,
-            final ChaiUser theUser,
-            final String userGUID
+            final ChaiUser theUser
 
     )
-            throws PwmOperationalException, ChaiUnavailableException
+            throws PwmUnrecoverableException
     {
         int attempts = 0;
         int successes = 0;
@@ -602,7 +599,9 @@ public class CrService extends AbstractPwmService implements PwmService
 
         LOGGER.debug( sessionLabel, () -> "will attempt to clear the following storage methods: "
                 + JsonFactory.get().serializeCollection( writeMethods ) + " for user " + theUser.getEntryDN()
-                + theUser.getEntryDN() + " guid=" + userGUID );
+                + theUser.getEntryDN() );
+
+        final String userGUID = readUserGuidIfNeeded( userIdentity, sessionLabel, writeMethods );
 
         for ( final DataStorageMethod loopWriteMethod : writeMethods )
         {
@@ -623,7 +622,7 @@ public class CrService extends AbstractPwmService implements PwmService
         {
             final String errorMsg = "no response save methods are available or configured";
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, errorMsg );
-            throw new PwmOperationalException( errorInfo );
+            throw new PwmUnrecoverableException( errorInfo );
         }
 
         if ( attempts != successes )
@@ -631,7 +630,7 @@ public class CrService extends AbstractPwmService implements PwmService
             // should be impossible to read here, but just in case.
             final String errorMsg = "response clear partially successful; attempts=" + attempts + ", successes=" + successes;
             final ErrorInformation errorInfo = new ErrorInformation( PwmError.ERROR_CLEARING_RESPONSES, errorMsg );
-            throw new PwmOperationalException( errorInfo );
+            throw new PwmUnrecoverableException( errorInfo );
         }
     }
 

+ 24 - 22
server/src/main/java/password/pwm/svc/email/EmailServerUtil.java

@@ -104,37 +104,39 @@ public class EmailServerUtil
     {
         final ProfileID id = profile.getId();
         final String address = profile.readSettingAsString( PwmSetting.EMAIL_SERVER_ADDRESS );
-        final int port = (int) profile.readSettingAsLong( PwmSetting.EMAIL_SERVER_PORT );
+        final int port = ( int ) profile.readSettingAsLong( PwmSetting.EMAIL_SERVER_PORT );
         final String username = profile.readSettingAsString( PwmSetting.EMAIL_USERNAME );
         final PasswordData password = profile.readSettingAsPassword( PwmSetting.EMAIL_PASSWORD );
 
         final SmtpServerType smtpServerType = profile.readSettingAsEnum( PwmSetting.EMAIL_SERVER_TYPE, SmtpServerType.class );
-        if ( StringUtil.notEmpty( address )
-                && port > 0
-        )
+
+        if ( StringUtil.isEmpty( address ) )
         {
-            final TrustManager[] effectiveTrustManagers = trustManagers == null
-                    ? trustManagerForProfile( appConfig, profile )
-                    : trustManagers;
-            final Properties properties = makeJavaMailProps( appConfig, profile, effectiveTrustManagers );
-            final jakarta.mail.Session session = jakarta.mail.Session.getInstance( properties, null );
-            return Optional.of( EmailServer.builder()
-                    .id( id )
-                    .host( address )
-                    .port( port )
-                    .username( username )
-                    .password( password )
-                    .javaMailProps( properties )
-                    .session( session )
-                    .type( smtpServerType )
-                    .build() );
+            LOGGER.debug( () -> "discarding incompletely configured email address for smtp server profile " + id + ", no server address" );
+            return Optional.empty();
         }
-        else
+
+        if ( port <= 0 )
         {
-            LOGGER.warn( () -> "discarding incompletely configured email address for smtp server profile " + id );
+            LOGGER.debug( () -> "discarding incompletely configured email address for smtp server profile " + id + ", missing port number" );
+            return Optional.empty();
         }
 
-        return Optional.empty();
+        final TrustManager[] effectiveTrustManagers = trustManagers == null
+                ? trustManagerForProfile( appConfig, profile )
+                : trustManagers;
+        final Properties properties = makeJavaMailProps( appConfig, profile, effectiveTrustManagers );
+        final jakarta.mail.Session session = jakarta.mail.Session.getInstance( properties, null );
+        return Optional.of( EmailServer.builder()
+                .id( id )
+                .host( address )
+                .port( port )
+                .username( username )
+                .password( password )
+                .javaMailProps( properties )
+                .session( session )
+                .type( smtpServerType )
+                .build() );
     }
 
     private static TrustManager[] trustManagerForProfile( final AppConfig appConfig, final EmailServerProfile emailServerProfile )

+ 20 - 15
server/src/main/java/password/pwm/svc/otp/OtpService.java

@@ -22,7 +22,9 @@ package password.pwm.svc.otp;
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import lombok.Getter;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Value;
 import org.apache.commons.codec.binary.Base32;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
@@ -499,13 +501,14 @@ public class OtpService extends AbstractPwmService implements PwmService
             final UserIdentity userIdentity
 
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
         final String userGUID;
         if ( otpSecretStorageLocations.contains( DataStorageMethod.DB ) || otpSecretStorageLocations.contains(
                 DataStorageMethod.LOCALDB ) )
         {
-            userGUID = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, false );
+            userGUID = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity )
+                    .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_GUID ) );
         }
         else
         {
@@ -514,7 +517,8 @@ public class OtpService extends AbstractPwmService implements PwmService
         return userGUID;
     }
 
-    @Getter
+    @Value
+    @Builder( access = AccessLevel.PRIVATE )
     public static class OtpSettings implements Serializable
     {
         private OTPStorageFormat otpStorageFormat;
@@ -530,17 +534,18 @@ public class OtpService extends AbstractPwmService implements PwmService
 
         static OtpSettings fromConfig( final DomainConfig config )
         {
-            final OtpSettings otpSettings = new OtpSettings();
-
-            otpSettings.otpStorageFormat = config.readSettingAsEnum( PwmSetting.OTP_SECRET_STORAGEFORMAT, OTPStorageFormat.class );
-            otpSettings.totpPastIntervals = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_PAST_INTERVALS ) );
-            otpSettings.totpFutureIntervals = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_FUTURE_INTERVALS ) );
-            otpSettings.totpIntervalSeconds = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_INTERVAL ) );
-            otpSettings.otpTokenLength = Integer.parseInt( config.readAppProperty( AppProperty.OTP_TOKEN_LENGTH ) );
-            otpSettings.recoveryTokenMacro = config.readAppProperty( AppProperty.OTP_RECOVERY_TOKEN_MACRO );
-            otpSettings.recoveryHashIterations = Integer.parseInt( config.readAppProperty( AppProperty.OTP_RECOVERY_HASH_COUNT ) );
-            otpSettings.recoveryHashMethod = config.readAppProperty( AppProperty.OTP_RECOVERY_HASH_METHOD );
-            return otpSettings;
+            final OtpSettings.OtpSettingsBuilder builder = OtpSettings.builder();
+
+            builder.otpStorageFormat = config.readSettingAsEnum( PwmSetting.OTP_SECRET_STORAGEFORMAT, OTPStorageFormat.class );
+            builder.totpPastIntervals = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_PAST_INTERVALS ) );
+            builder.totpFutureIntervals = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_FUTURE_INTERVALS ) );
+            builder.totpIntervalSeconds = Integer.parseInt( config.readAppProperty( AppProperty.TOTP_INTERVAL ) );
+            builder.otpTokenLength = Integer.parseInt( config.readAppProperty( AppProperty.OTP_TOKEN_LENGTH ) );
+            builder.recoveryTokenMacro = config.readAppProperty( AppProperty.OTP_RECOVERY_TOKEN_MACRO );
+            builder.recoveryHashIterations = Integer.parseInt( config.readAppProperty( AppProperty.OTP_RECOVERY_HASH_COUNT ) );
+            builder.recoveryHashMethod = config.readAppProperty( AppProperty.OTP_RECOVERY_HASH_METHOD );
+
+            return builder.build();
         }
     }
 }

+ 5 - 13
server/src/main/java/password/pwm/svc/pwnotify/PwNotifyDbStorageService.java

@@ -30,7 +30,6 @@ import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.svc.db.DatabaseException;
 import password.pwm.svc.db.DatabaseTable;
 import password.pwm.util.json.JsonFactory;
-import password.pwm.util.java.StringUtil;
 
 import java.util.Optional;
 
@@ -59,13 +58,10 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
     )
             throws PwmUnrecoverableException
     {
-        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, true );
-
-        if ( StringUtil.isEmpty( guid ) )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_MISSING_GUID );
-        }
+        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_GUID ) );
 
+        
         final Optional<String> rawDbValue;
         try
         {
@@ -87,12 +83,8 @@ class PwNotifyDbStorageService implements PwNotifyStorageService
     )
             throws PwmUnrecoverableException
     {
-        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, true );
-
-        if ( StringUtil.isEmpty( guid ) )
-        {
-            throw new PwmUnrecoverableException( PwmError.ERROR_MISSING_GUID );
-        }
+        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_GUID ) );
 
         final String rawDbValue = JsonFactory.get().serialize( pwNotifyUserStatus );
         try

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

@@ -72,7 +72,8 @@ class DatabaseUserHistory implements UserHistoryStore
             userIdentity = UserIdentity.create( auditRecord.getPerpetratorDN(), auditRecord.getPerpetratorLdapProfile(), auditRecord.getDomain() );
         }
 
-        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, null, userIdentity, false );
+        final String guid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity )
+                .orElseThrow( () -> PwmUnrecoverableException.newException( PwmError.ERROR_MISSING_GUID ) );
 
         try
         {

+ 1 - 3
server/src/main/java/password/pwm/util/cli/commands/ImportResponsesCommand.java

@@ -34,7 +34,6 @@ import password.pwm.config.profile.ChallengeProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.util.cli.CliException;
 import password.pwm.util.cli.CliParameters;
 import password.pwm.util.java.TimeDuration;
@@ -105,9 +104,8 @@ public class ImportResponsesCommand extends AbstractCliCommand
                         null, userIdentity, user, PwmPasswordPolicy.defaultPolicy(), PwmConstants.DEFAULT_LOCALE );
                 final ChallengeSet challengeSet = challengeProfile.getChallengeSet()
                         .orElseThrow( () -> new PwmUnrecoverableException( PwmError.ERROR_NO_CHALLENGES.toInfo() ) );
-                final String userGuid = LdapOperationsHelper.readLdapGuidValue( pwmDomain, null, userIdentity, false );
                 final ResponseInfoBean responseInfoBean = inputData.toResponseInfoBean( PwmConstants.DEFAULT_LOCALE, challengeSet.getIdentifier() );
-                pwmDomain.getCrService().writeResponses( null, userIdentity, user, userGuid, responseInfoBean );
+                pwmDomain.getCrService().writeResponses( SessionLabel.CLI_SESSION_LABEL, userIdentity, user, responseInfoBean );
             }
             catch ( final Exception e )
             {

+ 6 - 5
server/src/main/java/password/pwm/util/debug/CacheServiceDebugItemGenerator.java

@@ -20,8 +20,9 @@
 
 package password.pwm.util.debug;
 
-import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.PwmDomain;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.svc.cache.CacheService;
 import password.pwm.util.json.JsonFactory;
 import password.pwm.util.json.JsonProvider;
@@ -32,7 +33,7 @@ import java.io.Serializable;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-class CacheServiceDebugItemGenerator implements AppItemGenerator
+class CacheServiceDebugItemGenerator implements DomainItemGenerator
 {
     @Override
     public String getFilename()
@@ -41,10 +42,10 @@ class CacheServiceDebugItemGenerator implements AppItemGenerator
     }
 
     @Override
-    public void outputItem( final AppDebugItemInput debugItemInput, final OutputStream outputStream )
-            throws IOException
+    public void outputItem( final DomainDebugItemInput debugItemInput, final OutputStream outputStream )
+            throws IOException, PwmUnrecoverableException
     {
-        final PwmApplication pwmApplication = debugItemInput.getPwmApplication();
+        final PwmDomain pwmApplication = debugItemInput.getPwmDomain();
         final CacheService cacheService = pwmApplication.getCacheService();
 
         final Map<String, Serializable> debugOutput = new LinkedHashMap<>( cacheService.debugInfo() );

+ 1 - 1
server/src/main/java/password/pwm/util/debug/DebugItemGenerator.java

@@ -65,7 +65,6 @@ public class DebugItemGenerator
             LocalDBDebugGenerator.class,
             SessionDataGenerator.class,
             ClusterInfoDebugGenerator.class,
-            CacheServiceDebugItemGenerator.class,
             RootFileSystemDebugItemGenerator.class,
             StatisticsDataDebugItemGenerator.class,
             StatisticsEpsDataDebugItemGenerator.class,
@@ -73,6 +72,7 @@ public class DebugItemGenerator
 
     private static final List<Class<? extends DomainItemGenerator>> DOMAIN_ITEM_GENERATORS = List.of(
             LDAPPermissionItemGenerator.class,
+            CacheServiceDebugItemGenerator.class,
             LdapDebugItemGenerator.class,
             LdapRecentUserDebugGenerator.class,
             DashboardDataDebugItemGenerator.class,

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

@@ -592,8 +592,7 @@ public class PasswordUtility
 
         if ( settingClearResponses == HelpdeskClearResponseMode.yes )
         {
-            final String userGUID = LdapOperationsHelper.readLdapGuidValue( pwmDomain, sessionLabel, userIdentity, false );
-            pwmDomain.getCrService().clearResponses( pwmRequest.getLabel(), userIdentity, proxiedUser, userGUID );
+            pwmDomain.getCrService().clearResponses( pwmRequest.getLabel(), userIdentity, proxiedUser );
 
             // mark the event log
             final HelpdeskAuditRecord auditRecord = AuditRecordFactory.make( pwmRequest ).createHelpdeskAuditRecord(

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

@@ -42,7 +42,6 @@ import password.pwm.http.HttpContentType;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.svc.cr.CrService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsClient;
@@ -55,7 +54,6 @@ import password.pwm.ws.server.RestUtility;
 import password.pwm.ws.server.RestWebServer;
 
 import javax.servlet.annotation.WebServlet;
-import java.io.IOException;
 import java.io.Serializable;
 import java.time.Instant;
 import java.util.Collections;
@@ -257,19 +255,13 @@ public class RestChallengesServer extends RestServlet
         try
         {
             final ChaiUser chaiUser;
-            final String userGUID;
             final String csIdentifer;
             final UserIdentity userIdentity;
             final CrService crService = restRequest.getDomain().getCrService();
 
             userIdentity = targetUserIdentity.getUserIdentity();
             chaiUser = targetUserIdentity.getChaiUser();
-            userGUID = LdapOperationsHelper.readLdapGuidValue(
-                    restRequest.getDomain(),
-                    restRequest.getSessionLabel(),
-                    userIdentity,
-                    false
-            );
+
             final ChallengeProfile challengeProfile = crService.readUserChallengeProfile(
                     restRequest.getSessionLabel(),
                     userIdentity,
@@ -283,7 +275,7 @@ public class RestChallengesServer extends RestServlet
                     .getIdentifier();
 
             final ResponseInfoBean responseInfoBean = jsonInput.toResponseInfoBean( restRequest.getLocale(), csIdentifer );
-            crService.writeResponses( restRequest.getSessionLabel(), userIdentity, chaiUser, userGUID, responseInfoBean );
+            crService.writeResponses( restRequest.getSessionLabel(), userIdentity, chaiUser, responseInfoBean );
 
             // update statistics
             StatisticsClient.incrementStat( restRequest.getDomain(), Statistic.REST_CHALLENGES );
@@ -300,7 +292,7 @@ public class RestChallengesServer extends RestServlet
 
     @RestMethodHandler( method = HttpMethod.DELETE, produces = HttpContentType.json )
     public RestResultBean processJsonDeleteChallengeData( final RestRequest restRequest )
-            throws IOException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
         final String username = restRequest.readParameterAsString( FIELD_USERNAME );
 
@@ -310,28 +302,17 @@ public class RestChallengesServer extends RestServlet
     private RestResultBean doDeleteChallengeData( final RestRequest restRequest, final String username )
             throws PwmUnrecoverableException
     {
-
         final TargetUserIdentity targetUserIdentity = RestUtility.resolveRequestedUsername( restRequest, username );
 
         try
         {
-            final ChaiUser chaiUser;
-            final String userGUID;
-
-            chaiUser = targetUserIdentity.getChaiUser();
-            userGUID = LdapOperationsHelper.readLdapGuidValue(
-                    restRequest.getDomain(),
-                    restRequest.getSessionLabel(),
-                    targetUserIdentity.getUserIdentity(),
-                    false );
+            final ChaiUser chaiUser = targetUserIdentity.getChaiUser();
 
             final CrService crService = restRequest.getDomain().getCrService();
             crService.clearResponses(
                     restRequest.getSessionLabel(),
                     targetUserIdentity.getUserIdentity(),
-                    chaiUser,
-                    userGUID
-            );
+                    chaiUser );
 
             // update statistics
             StatisticsClient.incrementStat( restRequest.getDomain(), Statistic.REST_CHALLENGES );