浏览代码

user permission enhancements

Jason Rivard 4 年之前
父节点
当前提交
72af0f7b6f
共有 56 个文件被更改,包括 445 次插入268 次删除
  1. 2 1
      server/src/main/java/password/pwm/AppProperty.java
  2. 21 7
      server/src/main/java/password/pwm/bean/UserIdentity.java
  3. 14 11
      server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  4. 1 2
      server/src/main/java/password/pwm/config/profile/LdapProfile.java
  5. 2 2
      server/src/main/java/password/pwm/config/profile/ProfileUtility.java
  6. 28 0
      server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java
  7. 2 2
      server/src/main/java/password/pwm/config/value/UserPermissionValue.java
  8. 84 8
      server/src/main/java/password/pwm/health/ConfigurationChecker.java
  9. 2 0
      server/src/main/java/password/pwm/health/HealthMessage.java
  10. 2 2
      server/src/main/java/password/pwm/http/SessionManager.java
  11. 2 2
      server/src/main/java/password/pwm/http/bean/ConfigGuideBean.java
  12. 2 2
      server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  13. 2 2
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  14. 1 1
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  15. 11 6
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  16. 1 1
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideFormField.java
  17. 2 62
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  18. 57 1
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java
  19. 2 2
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  20. 2 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  21. 2 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  22. 9 2
      server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java
  23. 1 1
      server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java
  24. 6 20
      server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java
  25. 2 2
      server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java
  26. 90 6
      server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java
  27. 2 2
      server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  28. 1 1
      server/src/main/java/password/pwm/ldap/search/UserSearchJob.java
  29. 1 2
      server/src/main/java/password/pwm/ldap/search/UserSearchResults.java
  30. 2 2
      server/src/main/java/password/pwm/svc/PwmServiceManager.java
  31. 0 15
      server/src/main/java/password/pwm/svc/cache/CacheService.java
  32. 2 2
      server/src/main/java/password/pwm/svc/email/EmailServer.java
  33. 0 5
      server/src/main/java/password/pwm/svc/email/EmailServerUtil.java
  34. 2 2
      server/src/main/java/password/pwm/svc/email/EmailService.java
  35. 2 2
      server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java
  36. 2 2
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java
  37. 6 3
      server/src/main/java/password/pwm/svc/report/ReportService.java
  38. 2 1
      server/src/main/java/password/pwm/svc/report/ReportSummaryData.java
  39. 9 8
      server/src/main/java/password/pwm/svc/stats/StatisticsBundle.java
  40. 14 5
      server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java
  41. 16 17
      server/src/main/java/password/pwm/util/java/StatisticCounterBundle.java
  42. 5 0
      server/src/main/java/password/pwm/util/java/StringUtil.java
  43. 1 16
      server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java
  44. 2 2
      server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java
  45. 4 4
      server/src/main/java/password/pwm/util/operations/CrService.java
  46. 2 2
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  47. 3 3
      server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java
  48. 2 2
      server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java
  49. 2 1
      server/src/main/resources/password/pwm/AppProperty.properties
  50. 2 0
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  51. 2 2
      server/src/main/resources/password/pwm/i18n/ConfigGuide.properties
  52. 2 0
      server/src/main/resources/password/pwm/i18n/Health.properties
  53. 2 2
      webapp/src/main/webapp/WEB-INF/jsp/configguide-end.jsp
  54. 4 9
      webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp
  55. 0 6
      webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp
  56. 1 1
      webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js

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

@@ -74,7 +74,8 @@ public enum AppProperty
     CONFIG_THEME                                    ( "config.theme" ),
     CONFIG_JBCRYPT_PWLIB_ENABLE                     ( "config.enableJbCryptPwLibrary" ),
     CONFIG_EDITOR_BLOCK_OLD_IE                      ( "configEditor.blockOldIE" ),
-    CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT           ( "configEditor.queryFilter.testLimit" ),
+    CONFIG_EDITOR_USER_PERMISSION_MATCH_LIMIT       ( "configEditor.userPermission.matchResultsLimit" ),
+    CONFIG_EDITOR_USER_PERMISSION_TIMEOUT_SECONDS   ( "configEditor.userPermission.matchTimeoutSeconds" ),
     CONFIG_EDITOR_IDLE_TIMEOUT                      ( "configEditor.idleTimeoutSeconds" ),
     CONFIG_GUIDE_IDLE_TIMEOUT                       ( "configGuide.idleTimeoutSeconds" ),
     CONFIG_MANAGER_ZIPDEBUG_MAXLOGBYTES             ( "configManager.zipDebug.maxLogBytes" ),

+ 21 - 7
server/src/main/java/password/pwm/bean/UserIdentity.java

@@ -22,6 +22,7 @@ package password.pwm.bean;
 
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiException;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import org.jetbrains.annotations.NotNull;
 import password.pwm.PwmApplication;
 import password.pwm.config.Configuration;
@@ -39,6 +40,7 @@ import password.pwm.util.java.TimeDuration;
 import java.io.Serializable;
 import java.util.StringTokenizer;
 
+@SuppressFBWarnings( "SE_TRANSIENT_FIELD_NOT_RESTORED" )
 public class UserIdentity implements Serializable, Comparable<UserIdentity>
 {
     private static final long serialVersionUID = 1L;
@@ -49,8 +51,8 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
     private transient String obfuscatedValue;
     private transient boolean canonicalized;
 
-    private String userDN;
-    private String ldapProfile;
+    private final String userDN;
+    private final String ldapProfile;
 
     public UserIdentity( final String userDN, final String ldapProfile )
     {
@@ -62,6 +64,17 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
         this.ldapProfile = ldapProfile == null ? "" : ldapProfile;
     }
 
+    public UserIdentity( final String userDN, final String ldapProfile, final boolean canonical )
+    {
+        if ( userDN == null || userDN.length() < 1 )
+        {
+            throw new IllegalArgumentException( "UserIdentity: userDN value cannot be empty" );
+        }
+        this.userDN = userDN;
+        this.ldapProfile = ldapProfile == null ? "" : ldapProfile;
+        this.canonicalized = true;
+    }
+
     public String getUserDN( )
     {
         return userDN;
@@ -240,13 +253,14 @@ public class UserIdentity implements Serializable, Comparable<UserIdentity>
     }
 
     @Override
-    public int compareTo( @NotNull final UserIdentity o )
+    public int compareTo( @NotNull final UserIdentity otherIdentity )
     {
-        final String thisStr = ( ldapProfile == null ? "_" : ldapProfile ) + userDN;
-        final UserIdentity otherIdentity = ( UserIdentity ) o;
-        final String otherStr = ( otherIdentity.ldapProfile == null ? "_" : otherIdentity.ldapProfile ) + otherIdentity.userDN;
+        return compareString().compareToIgnoreCase( otherIdentity.compareString() );
+    }
 
-        return thisStr.compareTo( otherStr );
+    private String compareString()
+    {
+        return ( ldapProfile == null ? "_" : ldapProfile ) + "_" + userDN;
     }
 
     public UserIdentity canonicalized( final PwmApplication pwmApplication )

+ 14 - 11
server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -25,7 +25,6 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -40,8 +39,8 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.i18n.Display;
-import password.pwm.ldap.permission.UserPermissionTester;
 import password.pwm.ldap.permission.UserPermissionType;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -69,7 +68,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
 
         final Instant startSearchTime = Instant.now();
-        final int maxResultSize = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT ) );
+        final int maxResultSize = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_USER_PERMISSION_MATCH_LIMIT ) );
         final Collection<UserIdentity> users = discoverMatchingUsers( pwmApplication, maxResultSize, storedConfiguration.newStoredConfiguration(), setting, profile );
         final TimeDuration searchDuration = TimeDuration.fromCurrent( startSearchTime );
 
@@ -100,10 +99,11 @@ public class UserMatchViewerFunction implements SettingUIFunction
         final PwmApplication tempApplication = PwmApplication.createPwmApplication( pwmApplication.getPwmEnvironment().makeRuntimeInstance( config ) );
         final List<UserPermission> permissions = ( List<UserPermission> ) storedConfiguration.readSetting( setting, profile ).toNativeObject();
 
-       validateUserPermissionLdapValues( tempApplication, permissions );
+        validateUserPermissionLdapValues( tempApplication, permissions );
 
-        final TimeDuration maxSearchTime = TimeDuration.SECONDS_10;
-        return UserPermissionTester.discoverMatchingUsers( tempApplication, permissions, SessionLabel.SYSTEM_LABEL, maxResultSize, maxSearchTime );
+        final int maxSearchSeconds = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_USER_PERMISSION_TIMEOUT_SECONDS ) );
+        final TimeDuration maxSearchTime = TimeDuration.of( maxSearchSeconds, TimeDuration.Unit.SECONDS );
+        return UserPermissionUtility.discoverMatchingUsers( tempApplication, permissions, SessionLabel.SYSTEM_LABEL, maxResultSize, maxSearchTime );
     }
 
     private static void validateUserPermissionLdapValues(
@@ -133,11 +133,8 @@ public class UserMatchViewerFunction implements SettingUIFunction
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final Set<String> profileIDsToTest = new LinkedHashSet<>();
-        if ( profileID == null || profileID.isEmpty() )
-        {
-            profileIDsToTest.add( pwmApplication.getConfig().getDefaultLdapProfile().getIdentifier() );
-        }
-        else if ( profileID.equals( PwmConstants.PROFILE_ID_ALL ) )
+
+        if ( UserPermissionUtility.isAllProfiles( profileID ) )
         {
             profileIDsToTest.addAll( pwmApplication.getConfig().getLdapProfiles().keySet() );
         }
@@ -145,6 +142,12 @@ public class UserMatchViewerFunction implements SettingUIFunction
         {
             profileIDsToTest.add( profileID );
         }
+
+        if ( profileIDsToTest.isEmpty() )
+        {
+            throw new PwmOperationalException( new ErrorInformation( PwmError.ERROR_NO_PROFILE_ASSIGNED, "invalid ldap profile" ) );
+        }
+
         for ( final String loopID : profileIDsToTest )
         {
             ChaiEntry chaiEntry = null;

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

@@ -205,8 +205,7 @@ public class LdapProfile extends AbstractProfile implements Profile
 
         if ( !StringUtil.isEmpty( testUserDN ) )
         {
-            final String canonicalDN = readCanonicalDN( pwmApplication, testUserDN );
-            return new UserIdentity( canonicalDN, this.getIdentifier() );
+            return new UserIdentity( testUserDN, this.getIdentifier() ).canonicalized( pwmApplication );
         }
 
         return null;

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

@@ -30,7 +30,7 @@ import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.CommonValues;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.List;
@@ -81,7 +81,7 @@ public class ProfileUtility
         for ( final Profile profile : profileMap.values() )
         {
             final List<UserPermission> queryMatches = profile.getPermissionMatches();
-            final boolean match = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatches );
+            final boolean match = UserPermissionUtility.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatches );
             if ( match )
             {
                 return Optional.of( profile.getIdentifier() );

+ 28 - 0
server/src/main/java/password/pwm/config/stored/StoredConfigItemKey.java

@@ -31,9 +31,12 @@ import password.pwm.util.java.LazySupplier;
 import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class StoredConfigItemKey implements Serializable, Comparable<StoredConfigItemKey>
 {
@@ -104,6 +107,11 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
         return new StoredConfigItemKey( RecordType.PROPERTY, configurationProperty.getKey(), null );
     }
 
+    public boolean isRecordType( final RecordType recordType )
+    {
+        return recordType != null && Objects.equals( getRecordType(), recordType );
+    }
+
     public boolean isValid()
     {
         try
@@ -239,6 +247,8 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
         return toString().compareTo( o.toString() );
     }
 
+
+
     public PwmSettingSyntax getSyntax()
     {
         switch ( getRecordType() )
@@ -257,4 +267,22 @@ public class StoredConfigItemKey implements Serializable, Comparable<StoredConfi
                 throw new IllegalStateException();
         }
     }
+
+    public static Set<StoredConfigItemKey> filterBySettingSyntax( final PwmSettingSyntax pwmSettingSyntax, final Set<StoredConfigItemKey> input )
+    {
+        return Collections.unmodifiableSet( filterByType( RecordType.SETTING, input )
+                .stream()
+                .filter( ( k ) -> k.toPwmSetting().getSyntax() == pwmSettingSyntax )
+                .collect( Collectors.toSet() ) );
+    }
+
+    public static Set<StoredConfigItemKey> filterByType( final RecordType recordType, final Set<StoredConfigItemKey> input )
+    {
+        if ( JavaHelper.isEmpty( input ) )
+        {
+            return Collections.emptySet();
+        }
+
+        return Collections.unmodifiableSet( input.stream().filter( ( k ) -> k.isRecordType( recordType ) ).collect( Collectors.toSet() ) );
+    }
 }

+ 2 - 2
server/src/main/java/password/pwm/config/value/UserPermissionValue.java

@@ -27,7 +27,7 @@ import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.XmlElement;
@@ -145,7 +145,7 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
         {
             try
             {
-                 UserPermissionTester.validatePermissionSyntax( userPermission );
+                 UserPermissionUtility.validatePermissionSyntax( userPermission );
             }
             catch ( final PwmUnrecoverableException e )
             {

+ 84 - 8
server/src/main/java/password/pwm/health/ConfigurationChecker.java

@@ -27,20 +27,24 @@ import password.pwm.PwmConstants;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingSyntax;
-import password.pwm.config.profile.ChangePasswordProfile;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.config.option.MessageSendMethod;
 import password.pwm.config.profile.ActivateUserProfile;
+import password.pwm.config.profile.ChangePasswordProfile;
 import password.pwm.config.profile.ForgottenPasswordProfile;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.profile.NewUserProfile;
 import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.stored.StoredConfigItemKey;
+import password.pwm.config.stored.StoredConfiguration;
+import password.pwm.config.value.StoredValue;
+import password.pwm.config.value.ValueTypeConverter;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Config;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.StringUtil;
@@ -49,13 +53,13 @@ import password.pwm.util.password.PasswordUtility;
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -141,7 +145,8 @@ public class ConfigurationChecker implements HealthChecker
             VerifyIfDeprecatedSendMethodValuesUsed.class,
             VerifyIfDeprecatedJsFormOptionUsed.class,
             VerifyNewUserLdapProfile.class,
-            VerifyPasswordWaitTimes.class
+            VerifyPasswordWaitTimes.class,
+            VerifyUserPermissionSettings.class
     ) );
 
     static class VerifyBasicConfigs implements ConfigHealthCheck
@@ -240,7 +245,6 @@ public class ConfigurationChecker implements HealthChecker
             {
                 for ( final StoredConfigItemKey key : config.getStoredConfiguration().modifiedItems() )
                 {
-                    final Instant startTime = Instant.now();
                     if ( key.getRecordType() == StoredConfigItemKey.RecordType.SETTING )
                     {
                         final PwmSetting pwmSetting = key.toPwmSetting();
@@ -288,7 +292,7 @@ public class ConfigurationChecker implements HealthChecker
                     {
                             PwmSetting.FORGOTTEN_PASSWORD_READ_PREFERENCE,
                             PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE,
-                    };
+                            };
             for ( final PwmSetting loopSetting : interestedSettings )
             {
                 if ( config.getResponseStorageLocations( loopSetting ).contains( DataStorageMethod.LDAP ) )
@@ -326,7 +330,7 @@ public class ConfigurationChecker implements HealthChecker
                             PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE,
                             PwmSetting.INTRUDER_STORAGE_METHOD,
                             PwmSetting.EVENTS_USER_STORAGE_METHOD,
-                    };
+                            };
 
                     for ( final PwmSetting loopSetting : settingsToCheck )
                     {
@@ -570,9 +574,81 @@ public class ConfigurationChecker implements HealthChecker
                     ) );
                 }
             }
+            return records;
+        }
+    }
 
 
-            return records;
+
+    static class VerifyUserPermissionSettings implements ConfigHealthCheck
+    {
+        @Override
+        public List<HealthRecord> healthCheck( final Configuration config, final Locale locale )
+        {
+            final List<HealthRecord> records = new ArrayList<>();
+
+            final StoredConfiguration storedConfiguration = config.getStoredConfiguration();
+            for ( final StoredConfigItemKey configItemKey : StoredConfigItemKey.filterBySettingSyntax(
+                    PwmSettingSyntax.USER_PERMISSION,
+                    storedConfiguration.modifiedItems() ) )
+            {
+                final StoredValue storedValue = storedConfiguration.readStoredValue( configItemKey ).orElseThrow( NoSuchElementException::new );
+                final List<UserPermission> permissions = ValueTypeConverter.valueToUserPermissions( storedValue );
+                for ( final UserPermission permission : permissions )
+                {
+                    try
+                    {
+                        UserPermissionUtility.validatePermissionSyntax( permission );
+                    }
+                    catch ( final PwmUnrecoverableException e )
+                    {
+                        final PwmSetting pwmSetting = configItemKey.toPwmSetting();
+                        records.add( HealthRecord.forMessage(
+                                HealthMessage.Config_SettingIssue,
+                                pwmSetting.toMenuLocationDebug( configItemKey.getProfileID(), locale ),
+                                e.getMessage() ) );
+                    }
+
+                    records.addAll( checkLdapProfile( config, configItemKey, locale, permission ) );
+                }
+            }
+
+            return Collections.unmodifiableList( records );
+        }
+
+        private static List<HealthRecord> checkLdapProfile(
+                final Configuration configuration,
+                final StoredConfigItemKey storedConfigItemKey,
+                final Locale locale,
+                final UserPermission permission
+        )
+        {
+            final List<LdapProfile> ldapProfiles = ldapProfilesForLdapProfileSetting( configuration, permission.getLdapProfileID() );
+            if ( ldapProfiles.isEmpty()  )
+            {
+                final PwmSetting pwmSetting = storedConfigItemKey.toPwmSetting();
+                return Collections.singletonList( HealthRecord.forMessage(
+                        HealthMessage.Config_ProfileValueValidity,
+                        pwmSetting.toMenuLocationDebug( storedConfigItemKey.getProfileID(), locale ),
+                        permission.getLdapProfileID() ) );
+            }
+
+            return Collections.emptyList();
+        }
+
+        public static List<LdapProfile> ldapProfilesForLdapProfileSetting( final Configuration configuration, final String profileID )
+        {
+            if ( UserPermissionUtility.isAllProfiles( profileID ) )
+            {
+                return Collections.unmodifiableList( new ArrayList<>( configuration.getLdapProfiles().values() ) );
+            }
+
+            if ( configuration.getLdapProfiles().containsKey( profileID ) )
+            {
+                return Collections.singletonList( configuration.getLdapProfiles().get( profileID ) );
+            }
+
+            return Collections.emptyList();
         }
     }
 

+ 2 - 0
server/src/main/java/password/pwm/health/HealthMessage.java

@@ -68,6 +68,8 @@ public enum HealthMessage
     Config_PasswordPolicyProblem( HealthStatus.CONFIG, HealthTopic.Configuration ),
     Config_UserPermissionValidity( HealthStatus.CONFIG, HealthTopic.Configuration ),
     Config_DNValueValidity( HealthStatus.CONFIG, HealthTopic.Configuration ),
+    Config_ProfileValueValidity( HealthStatus.CONFIG, HealthTopic.Configuration ),
+    Config_SettingIssue( HealthStatus.CAUTION, HealthTopic.Configuration ),
     Config_NoRecoveryEnabled( HealthStatus.CAUTION, HealthTopic.Configuration ),
     Config_Certificate( HealthStatus.WARN, HealthTopic.Configuration ),
     Config_InvalidSendMethod( HealthStatus.CAUTION, HealthTopic.Configuration ),

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

@@ -41,7 +41,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.util.PasswordData;
@@ -226,7 +226,7 @@ public class SessionManager
 
             final PwmSetting setting = permission.getPwmSetting();
             final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission( setting );
-            final boolean result = UserPermissionTester.testUserPermission( pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
+            final boolean result = UserPermissionUtility.testUserPermission( pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
             status = result ? Permission.PermissionStatus.GRANTED : Permission.PermissionStatus.DENIED;
             pwmSession.getUserSessionDataCacheBean().setPermission( permission, status );
 

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

@@ -30,7 +30,7 @@ import password.pwm.http.servlet.configguide.GuideStep;
 
 import java.security.cert.X509Certificate;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -41,7 +41,7 @@ public class ConfigGuideBean extends PwmSessionBean
 {
 
     private GuideStep step = GuideStep.START;
-    private final Map<ConfigGuideFormField, String> formData = new HashMap<>( ConfigGuideForm.defaultForm() );
+    private final Map<ConfigGuideFormField, String> formData = new EnumMap<>( ConfigGuideForm.defaultForm() );
     private List<X509Certificate> ldapCertificates;
     private boolean certsTrustedbyKeystore = false;
     private boolean useConfiguredCerts = false;

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

@@ -36,7 +36,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ShortcutsBean;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.java.JavaHelper;
@@ -193,7 +193,7 @@ public class ShortcutServlet extends AbstractPwmServlet
                         .ldapBase( userIdentity.getLdapProfileID() )
                         .build();
 
-                final boolean queryMatch = UserPermissionTester.testUserPermission(
+                final boolean queryMatch = UserPermissionUtility.testUserPermission(
                         pwmRequest.commonValues(),
                         pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
                         userPermission

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

@@ -33,7 +33,7 @@ import password.pwm.config.profile.PwmPasswordPolicy;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.svc.PwmService;
@@ -122,7 +122,7 @@ public class UserDebugDataReader
             if ( !setting.isHidden() && !setting.getCategory().isHidden() && !setting.getCategory().hasProfiles() )
             {
                 final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission( permission.getPwmSetting() );
-                final boolean result = UserPermissionTester.testUserPermission(
+                final boolean result = UserPermissionUtility.testUserPermission(
                         pwmApplication,
                         sessionLabel,
                         userIdentity,

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

@@ -912,7 +912,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
         final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap( PwmHttpRequestWrapper.Flag.BypassValidation );
         final String profile = inputMap.get( "profile" );
-        final String dn = inputMap.containsKey( "dn" ) ? inputMap.get( "dn" ) : "";
+        final String dn = inputMap.getOrDefault( "dn", "" );
 
         final LdapBrowser ldapBrowser = new LdapBrowser(
                 pwmRequest.getPwmApplication().getLdapConnectionService().getChaiProviderFactory(),

+ 11 - 6
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java

@@ -22,7 +22,6 @@ package password.pwm.http.servlet.configguide;
 
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingTemplate;
-import password.pwm.config.value.StoredValue;
 import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationFactory;
 import password.pwm.config.stored.StoredConfigurationModifier;
@@ -30,6 +29,7 @@ import password.pwm.config.value.BooleanValue;
 import password.pwm.config.value.ChallengeValue;
 import password.pwm.config.value.FileValue;
 import password.pwm.config.value.PasswordValue;
+import password.pwm.config.value.StoredValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.UserPermissionValue;
@@ -45,7 +45,7 @@ import password.pwm.util.logging.PwmLogger;
 
 import java.net.URI;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 
@@ -56,7 +56,7 @@ public class ConfigGuideForm
 
     public static Map<ConfigGuideFormField, String> defaultForm( )
     {
-        final Map<ConfigGuideFormField, String> defaultLdapForm = new HashMap<>();
+        final Map<ConfigGuideFormField, String> defaultLdapForm = new EnumMap<>( ConfigGuideFormField.class );
         for ( final ConfigGuideFormField formParameter : ConfigGuideFormField.values() )
         {
             defaultLdapForm.put( formParameter, "" );
@@ -66,6 +66,11 @@ public class ConfigGuideForm
         defaultLdapForm.put( ConfigGuideFormField.PARAM_LDAP_SECURE, "true" );
         defaultLdapForm.remove( ConfigGuideFormField.CHALLENGE_RESPONSE_DATA );
 
+        defaultLdapForm.put( ConfigGuideFormField.PARAM_LDAP_HOST, "172.17.2.91" );
+        defaultLdapForm.put( ConfigGuideFormField.PARAM_LDAP_CONTEXT, "ou=users,o=data" );
+        defaultLdapForm.put( ConfigGuideFormField.PARAM_LDAP_PROXY_DN, "cn=pwm-proxy,ou=sa,o=system" );
+        defaultLdapForm.put( ConfigGuideFormField.PARAM_LDAP_PROXY_PW, "gcms1234" );
+
         return Collections.unmodifiableMap( defaultLdapForm );
     }
 
@@ -172,10 +177,10 @@ public class ConfigGuideForm
 
         {
             // set admin query
-            final String groupDN = formData.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP );
+            final String userDN = formData.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_USER );
             final List<UserPermission> userPermissions = Collections.singletonList( UserPermission.builder()
-                    .type( UserPermissionType.ldapGroup )
-                    .ldapBase( groupDN )
+                    .type( UserPermissionType.ldapUser )
+                    .ldapBase( userDN )
                     .build() );
             storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, new UserPermissionValue( userPermissions ), null );
         }

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

@@ -38,7 +38,7 @@ public enum ConfigGuideFormField
     PARAM_LDAP_CONTEXT( PwmSetting.LDAP_CONTEXTLESS_ROOT ),
     PARAM_LDAP_TEST_USER_ENABLED( null ),
     PARAM_LDAP_TEST_USER( PwmSetting.LDAP_TEST_USER_DN ),
-    PARAM_LDAP_ADMIN_GROUP( PwmSetting.QUERY_MATCH_PWM_ADMIN ),
+    PARAM_LDAP_ADMIN_USER( PwmSetting.QUERY_MATCH_PWM_ADMIN ),
 
     PARAM_DB_CLASSNAME( PwmSetting.DATABASE_CLASS ),
     PARAM_DB_CONNECT_URL( PwmSetting.DATABASE_URL ),

+ 2 - 62
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java

@@ -26,11 +26,9 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
-import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.StoredValue;
-import password.pwm.config.function.UserMatchViewerFunction;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfiguration;
@@ -109,7 +107,6 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         useConfiguredCerts( HttpMethod.POST ),
         uploadConfig( HttpMethod.POST ),
         extendSchema( HttpMethod.POST ),
-        viewAdminMatches( HttpMethod.POST ),
         browseLdap( HttpMethod.POST ),
         uploadJDBCDriver( HttpMethod.POST ),
         skipGuide( HttpMethod.POST ),
@@ -137,7 +134,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         return ConfigGuideAction.class;
     }
 
-    private ConfigGuideBean getBean( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
+    static ConfigGuideBean getBean( final PwmRequest pwmRequest ) throws PwmUnrecoverableException
     {
         return pwmRequest.getPwmApplication().getSessionStateService().getBean( pwmRequest, ConfigGuideBean.class );
     }
@@ -281,34 +278,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
 
             case LDAP_ADMINS:
             {
-                try
-                {
-                    final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
-                    final Collection<UserIdentity> results = userMatchViewerFunction.discoverMatchingUsers(
-                            pwmRequest.getPwmApplication(),
-                            2,
-                            storedConfiguration,
-                            PwmSetting.QUERY_MATCH_PWM_ADMIN,
-                            null
-                    );
-
-                    if ( results.isEmpty() )
-                    {
-                        records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "No matching admin users" ) );
-                    }
-                    else
-                    {
-                        records.add( new HealthRecord( HealthStatus.GOOD, HealthTopic.LDAP, "Admin group validated" ) );
-                    }
-                }
-                catch ( final PwmException e )
-                {
-                    records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getErrorInformation().toDebugStr() ) );
-                }
-                catch ( final Exception e )
-                {
-                    records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getMessage() ) );
-                }
+                records.addAll( ConfigGuideUtils.checkAdminHealth( pwmRequest, storedConfiguration ) );
             }
             break;
 
@@ -347,36 +317,6 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         return ProcessStatus.Halt;
     }
 
-    @ActionHandler( action = "viewAdminMatches" )
-    private ProcessStatus restViewAdminMatches(
-            final PwmRequest pwmRequest
-    )
-            throws IOException, ServletException, PwmUnrecoverableException
-    {
-        final ConfigGuideBean configGuideBean = getBean( pwmRequest );
-
-        try
-        {
-            final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
-            final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
-            final StoredConfigurationModifier modifier = StoredConfigurationModifier.newModifier( storedConfiguration );
-            final Serializable output = userMatchViewerFunction.provideFunction( pwmRequest, modifier, PwmSetting.QUERY_MATCH_PWM_ADMIN, null, null );
-            pwmRequest.outputJsonResult( RestResultBean.withData( output ) );
-        }
-        catch ( final PwmException e )
-        {
-            LOGGER.error( pwmRequest, e.getErrorInformation() );
-            pwmRequest.respondWithError( e.getErrorInformation(), false );
-        }
-        catch ( final Exception e )
-        {
-            final ErrorInformation errorInformation = new ErrorInformation( PwmError.ERROR_INTERNAL, "error while testing matches = " + e.getMessage() );
-            LOGGER.error( pwmRequest, errorInformation );
-            pwmRequest.respondWithError( errorInformation );
-        }
-        return ProcessStatus.Halt;
-    }
-
     @ActionHandler( action = "browseLdap" )
     private ProcessStatus restBrowseLdap(
             final PwmRequest pwmRequest

+ 57 - 1
server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideUtils.java

@@ -27,7 +27,10 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.function.UserMatchViewerFunction;
 import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.ConfigurationReader;
 import password.pwm.config.stored.StoredConfiguration;
@@ -39,6 +42,9 @@ 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.health.HealthStatus;
+import password.pwm.health.HealthTopic;
 import password.pwm.http.ContextManager;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestAttribute;
@@ -62,6 +68,8 @@ import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -225,7 +233,7 @@ public class ConfigGuideUtils
             final ErrorInformation errorInformation = new ErrorInformation( PwmError.CONFIG_UPLOAD_FAILURE, errorMsg, new String[]
                     {
                             errorMsg,
-                    }
+                            }
             );
             pwmRequest.respondWithError( errorInformation, true );
         }
@@ -266,4 +274,52 @@ public class ConfigGuideUtils
         }
     }
 
+    static List<HealthRecord> checkAdminHealth( final PwmRequest pwmRequest, final StoredConfiguration storedConfiguration )
+    {
+        final List<HealthRecord> records = new ArrayList<>();
+
+        try
+        {
+            final ConfigGuideBean configGuideBean = ConfigGuideServlet.getBean( pwmRequest );
+            final Map<ConfigGuideFormField, String> form = configGuideBean.getFormData();
+            final PwmApplication tempApplication = PwmApplication.createPwmApplication(
+                    pwmRequest.getPwmApplication().getPwmEnvironment().makeRuntimeInstance( new Configuration( storedConfiguration ) ) );
+
+            final String adminDN = form.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_USER );
+            final UserIdentity adminIdentity = new UserIdentity( adminDN, PwmConstants.PROFILE_ID_DEFAULT );
+
+            final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
+            final Collection<UserIdentity> results = userMatchViewerFunction.discoverMatchingUsers(
+                    tempApplication,
+                    1,
+                    storedConfiguration,
+                    PwmSetting.QUERY_MATCH_PWM_ADMIN,
+                    null
+            );
+
+            if ( !results.isEmpty() )
+            {
+                final UserIdentity foundIdentity = results.iterator().next();
+                if ( foundIdentity.canonicalEquals( adminIdentity, tempApplication ) )
+                {
+                    records.add( new HealthRecord( HealthStatus.GOOD, HealthTopic.LDAP, "Admin user validated." ) );
+                }
+            }
+        }
+        catch ( final PwmException e )
+        {
+            records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin user validation: " + e.getErrorInformation().toDebugStr() ) );
+        }
+        catch ( final Exception e )
+        {
+            records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Error during admin user validation: " + e.getMessage() ) );
+        }
+
+        if ( records.isEmpty() )
+        {
+            records.add( new HealthRecord( HealthStatus.WARN, HealthTopic.LDAP, "Admin user not found." ) );
+        }
+
+        return records;
+    }
 }

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

@@ -37,7 +37,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.ldap.permission.UserPermissionType;
@@ -187,7 +187,7 @@ public class HelpdeskServletUtil
                 .ldapProfileID( userIdentity.getLdapProfileID() )
                 .build();
 
-        final boolean match = UserPermissionTester.testUserPermission(
+        final boolean match = UserPermissionUtility.testUserPermission(
                 pwmRequest.commonValues(),
                 userIdentity,
                 userPermission

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

@@ -49,7 +49,7 @@ import password.pwm.http.servlet.peoplesearch.bean.SearchResultBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserDetailBean;
 import password.pwm.http.servlet.peoplesearch.bean.UserReferenceBean;
 import password.pwm.i18n.Display;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.PhotoDataBean;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
@@ -623,7 +623,7 @@ class PeopleSearchDataReader
                     .ldapProfileID( userIdentity.getLdapProfileID() )
                     .build();
 
-            return UserPermissionTester.testUserPermission( pwmRequest.commonValues(), userIdentity, userPermission );
+            return UserPermissionUtility.testUserPermission( pwmRequest.commonValues(), userIdentity, userPermission );
         };
 
         final boolean result = storeDataInCache( CacheIdentifier.checkIfViewable, userIdentity.toDelimitedKey(), Boolean.class, cacheLoader );

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

@@ -39,7 +39,7 @@ import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ImmutableByteArray;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.PhotoDataBean;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
@@ -123,7 +123,7 @@ public class PhotoDataReader
             return true;
         }
 
-        final boolean hasPermission = UserPermissionTester.testUserPermission( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userIdentity, permissions );
+        final boolean hasPermission = UserPermissionUtility.testUserPermission( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userIdentity, permissions );
         if ( !hasPermission )
         {
             LOGGER.debug( pwmRequest, () -> "user " + userIdentity + " failed photo query filter, denying photo view ("

+ 9 - 2
server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java

@@ -42,16 +42,23 @@ class AllPermissionTypeHelper implements PermissionTypeHelper
     }
 
     @Override
-    public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission )
+            throws PwmUnrecoverableException
     {
+        final String profileID = UserPermissionUtility.isAllProfiles( userPermission.getLdapProfileID() )
+                ? null
+                : userPermission.getLdapProfileID();
+
         return SearchConfiguration.builder()
                 .username( "*" )
+                .ldapProfile( profileID )
                 .enableValueEscaping( false )
                 .build();
     }
 
     @Override
-    public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    public void validatePermission( final UserPermission userPermission )
+            throws PwmUnrecoverableException
     {
 
     }

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

@@ -89,7 +89,7 @@ class LdapGroupTypeHelper implements PermissionTypeHelper
     {
         return SearchConfiguration.builder()
                 .groupDN( userPermission.getLdapBase() )
-                .ldapProfile( UserPermissionTester.profileIdForPermission( userPermission ) )
+                .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ) )
                 .build();
     }
 

+ 6 - 20
server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java

@@ -26,7 +26,6 @@ import com.novell.ldapchai.provider.SearchScope;
 import password.pwm.PwmApplication;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
@@ -40,7 +39,7 @@ import java.util.Map;
 class LdapQueryHelper implements PermissionTypeHelper
 {
 
-    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionTester.class );
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionUtility.class );
 
     public boolean testMatch(
             final PwmApplication pwmApplication,
@@ -52,7 +51,10 @@ class LdapQueryHelper implements PermissionTypeHelper
     {
         if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().trim().isEmpty() )
         {
-            if ( !testBaseDnMatch( pwmApplication, userPermission.getLdapBase(), userIdentity ) )
+            final String canonicalBaseDN = pwmApplication.getConfig().getLdapProfiles().get( userIdentity.getLdapProfileID() )
+                    .readCanonicalDN( pwmApplication, userPermission.getLdapBase() );
+
+            if ( !UserPermissionUtility.testBaseDnMatch( pwmApplication, canonicalBaseDN, userIdentity ) )
             {
                 return false;
             }
@@ -113,30 +115,14 @@ class LdapQueryHelper implements PermissionTypeHelper
     }
 
 
-    private static boolean testBaseDnMatch(
-            final PwmApplication pwmApplication,
-            final String baseDN,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        if ( baseDN == null || baseDN.trim().isEmpty() )
-        {
-            return true;
-        }
 
-        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
-        final String canonicalBaseDN = ldapProfile.readCanonicalDN( pwmApplication, baseDN );
-        final String userDN = userIdentity.getUserDN();
-        return userDN.endsWith( canonicalBaseDN );
-    }
 
     public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission )
             throws PwmUnrecoverableException
     {
         return SearchConfiguration.builder()
                 .filter( userPermission.getLdapQuery() )
-                .ldapProfile( UserPermissionTester.profileIdForPermission( userPermission ) )
+                .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ) )
                 .build();
     }
 

+ 2 - 2
server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java

@@ -70,9 +70,9 @@ class LdapUserDNTypeHelper implements PermissionTypeHelper
     public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException
     {
         return SearchConfiguration.builder()
-                .filter( "(objectClass=*)" )
+                .username( "*" )
                 .enableContextValidation( false )
-                .ldapProfile( UserPermissionTester.profileIdForPermission( userPermission ) )
+                .ldapProfile( UserPermissionUtility.profileIdForPermission( userPermission ) )
                 .contexts( Collections.singletonList( userPermission.getLdapBase() ) )
                 .searchScope( SearchConfiguration.SearchScope.base )
                 .build();

+ 90 - 6
server/src/main/java/password/pwm/ldap/permission/UserPermissionTester.java → server/src/main/java/password/pwm/ldap/permission/UserPermissionUtility.java

@@ -20,10 +20,12 @@
 
 package password.pwm.ldap.permission;
 
+import com.novell.ldapchai.util.StringHelper;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
 import password.pwm.bean.UserIdentity;
+import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -31,6 +33,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.CommonValues;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
+import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -40,10 +43,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
-public class UserPermissionTester
+public class UserPermissionUtility
 {
-    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionTester.class );
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionUtility.class );
 
     public static boolean testUserPermission(
             final CommonValues commonValues,
@@ -118,8 +122,8 @@ public class UserPermissionTester
         final Instant startTime = Instant.now();
         final boolean match = permissionTypeHelper.testMatch( pwmApplication, sessionLabel, userIdentity, userPermission );
         LOGGER.debug( sessionLabel, () -> "user " + userIdentity.toDisplayString() + " is "
-                + ( match ? "" : "not " )
-                + "a match for permission '" + userPermission + "'",
+                        + ( match ? "" : "not " )
+                        + "a match for permission '" + userPermission + "'",
                 () -> TimeDuration.fromCurrent( startTime ) );
         return match;
     }
@@ -173,7 +177,10 @@ public class UserPermissionTester
             }
         }
 
-        return Collections.unmodifiableList( resultSet );
+        final List<UserIdentity> strippedResults = stripUserMatchesOutsideUserContexts( sessionLabel, pwmApplication, resultSet );
+        final List<UserIdentity> sortedResults = new ArrayList<>( strippedResults );
+        Collections.sort( sortedResults );
+        return Collections.unmodifiableList( sortedResults );
     }
 
     static String profileIdForPermission( final UserPermission userPermission )
@@ -188,7 +195,6 @@ public class UserPermissionTester
         return null;
     }
 
-
     public static void validatePermissionSyntax( final UserPermission userPermission )
             throws PwmUnrecoverableException
     {
@@ -202,4 +208,82 @@ public class UserPermissionTester
         final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester();
         permissionTypeHelper.validatePermission( userPermission );
     }
+
+    static List<UserIdentity> stripUserMatchesOutsideUserContexts(
+            final SessionLabel sessionLabel,
+            final PwmApplication pwmApplication,
+            final List<UserIdentity> userIdentities
+    )
+    {
+        final Instant startTime = Instant.now();
+        final List<UserIdentity> output = userIdentities
+                .stream()
+                .filter( ( u ) -> testUserWithinConfiguredUserContexts( sessionLabel, pwmApplication, u ) )
+                .collect( Collectors.toList() );
+
+        final int removedValues = userIdentities.size() - output.size();
+        if ( removedValues > 0 )
+        {
+            LOGGER.debug(
+                    sessionLabel,
+                    () -> "stripped " + removedValues + " user(s) from set of " + userIdentities.size() + " permission matches",
+                    () -> TimeDuration.fromCurrent( startTime ) );
+        }
+        return Collections.unmodifiableList( output );
+    }
+
+    public static boolean testUserWithinConfiguredUserContexts(
+            final SessionLabel sessionLabel,
+            final PwmApplication pwmApplication,
+            final UserIdentity userIdentity
+    )
+    {
+        final String ldapProfileID = userIdentity.getLdapProfileID();
+        final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get( ldapProfileID );
+        try
+        {
+            final List<String> rootContexts = ldapProfile.getRootContexts( pwmApplication );
+
+            for ( final String rootContext : rootContexts )
+            {
+                if ( testBaseDnMatch( pwmApplication, rootContext, userIdentity ) )
+                {
+                    return true;
+                }
+            }
+        }
+        catch ( final PwmUnrecoverableException e )
+        {
+            LOGGER.debug( sessionLabel,
+                    () -> "unexpected error testing userIdentity " + userIdentity.toDisplayString() + " for configured ldapProfile user context match" );
+        }
+
+        LOGGER.trace( sessionLabel, () -> "stripping user " + userIdentity.toDisplayString()
+                + " from permission list because it is not contained by configured user contexts in ldapProfile " + ldapProfileID  );
+
+        return false;
+    }
+
+    static boolean testBaseDnMatch(
+            final PwmApplication pwmApplication,
+            final String canonicalBaseDN,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( StringUtil.isTrimEmpty( canonicalBaseDN ) )
+        {
+            return false;
+        }
+
+        final String userDN = userIdentity.canonicalized( pwmApplication ).getUserDN();
+        return userDN.endsWith( canonicalBaseDN );
+    }
+
+    public static boolean isAllProfiles( final String profile )
+    {
+        return StringHelper.isEmpty( profile ) || PwmConstants.PROFILE_ID_ALL.equalsIgnoreCase( profile );
+    }
+
+
 }

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

@@ -46,7 +46,7 @@ import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.ConditionalTaskExecutor;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogLevel;
@@ -78,7 +78,7 @@ public class UserSearchEngine implements PwmService
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( UserSearchEngine.class );
 
-    private final StatisticIntCounterMap<SearchStatistic> counters = new StatisticIntCounterMap<>( SearchStatistic.class );
+    private final StatisticCounterBundle<SearchStatistic> counters = new StatisticCounterBundle<>( SearchStatistic.class );
     private final AtomicLoopIntIncrementer searchIdCounter = new AtomicLoopIntIncrementer();
 
     enum SearchStatistic

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

@@ -128,7 +128,7 @@ class UserSearchJob implements Callable<Map<UserIdentity, Map<String, String>>>
         {
             final String userDN = entry.getKey();
             final Map<String, String> attributeMap = entry.getValue();
-            final UserIdentity userIdentity = new UserIdentity( userDN, userSearchJobParameters.getLdapProfile().getIdentifier() );
+            final UserIdentity userIdentity = new UserIdentity( userDN, userSearchJobParameters.getLdapProfile().getIdentifier(), true );
             returnMap.put( userIdentity, attributeMap );
         }
         return returnMap;

+ 1 - 2
server/src/main/java/password/pwm/ldap/search/UserSearchResults.java

@@ -84,8 +84,7 @@ public class UserSearchResults implements Serializable
             }
         };
 
-        final List<UserIdentity> identitySortMap = new ArrayList<>();
-        identitySortMap.addAll( results.keySet() );
+        final List<UserIdentity> identitySortMap = new ArrayList<>( results.keySet() );
         identitySortMap.sort( comparator );
 
         final Map<UserIdentity, Map<String, String>> sortedResults = new LinkedHashMap<>();

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

@@ -26,7 +26,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
@@ -72,7 +72,7 @@ public class PwmServiceManager
                 || pwmApplication.getPwmEnvironment().getFlags().contains( PwmEnvironment.ApplicationFlag.CommandLineInstance );
 
         final String logVerb = initialized ? "restart" : "start";
-        final StatisticIntCounterMap<InitializationStats> statCounter = new StatisticIntCounterMap<>( InitializationStats.class );
+        final StatisticCounterBundle<InitializationStats> statCounter = new StatisticCounterBundle<>( InitializationStats.class );
         LOGGER.trace( () -> "beginning service " + logVerb + " process" );
 
         for ( final PwmServiceEnum serviceClassEnum : PwmServiceEnum.values() )

+ 0 - 15
server/src/main/java/password/pwm/svc/cache/CacheService.java

@@ -22,7 +22,6 @@ package password.pwm.svc.cache;
 
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
-import password.pwm.PwmApplicationMode;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
@@ -71,20 +70,6 @@ public class CacheService implements PwmService
             return;
         }
 
-        if ( pwmApplication.getLocalDB() == null )
-        {
-            LOGGER.debug( () -> "skipping cache service init due to localDB not being available" );
-            status = STATUS.CLOSED;
-            return;
-        }
-
-        if ( pwmApplication.getApplicationMode() == PwmApplicationMode.READ_ONLY )
-        {
-            LOGGER.debug( () -> "skipping cache service init due to read-only application mode" );
-            status = STATUS.CLOSED;
-            return;
-        }
-
         final int maxMemItems = Integer.parseInt( pwmApplication.getConfig().readAppProperty( AppProperty.CACHE_MEMORY_MAX_ITEMS ) );
         memoryCacheStore = new MemoryCacheStore( maxMemItems );
         this.traceDebugOutputter = new ConditionalTaskExecutor(

+ 2 - 2
server/src/main/java/password/pwm/svc/email/EmailServer.java

@@ -26,7 +26,7 @@ import password.pwm.config.option.SmtpServerType;
 import password.pwm.error.ErrorInformation;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.MovingAverage;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 
@@ -48,7 +48,7 @@ public class EmailServer
     private javax.mail.Session session;
     private SmtpServerType type;
 
-    private final StatisticIntCounterMap<ServerStat> connectionStats = new StatisticIntCounterMap<>( ServerStat.class );
+    private final StatisticCounterBundle<ServerStat> connectionStats = new StatisticCounterBundle<>( ServerStat.class );
     private final MovingAverage averageSendTime = new MovingAverage( TimeDuration.MINUTE );
     private final AtomicReference<ErrorInformation> lastConnectError = new AtomicReference();
 

+ 0 - 5
server/src/main/java/password/pwm/svc/email/EmailServerUtil.java

@@ -447,11 +447,6 @@ public class EmailServerUtil
             }
         }
 
-        if ( records.isEmpty() )
-        {
-            records.add( HealthRecord.forMessage( HealthMessage.EMail_OK ) );
-        }
-
         return Collections.unmodifiableList( records );
     }
 }

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

@@ -39,7 +39,7 @@ import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.java.ConditionalTaskExecutor;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.localdb.LocalDB;
@@ -251,7 +251,7 @@ public class EmailService implements PwmService
 
             for ( final EmailServer emailServer : connectionPool.getServers() )
             {
-                final StatisticIntCounterMap<EmailServer.ServerStat> serverStats = emailServer.getConnectionStats();
+                final StatisticCounterBundle<EmailServer.ServerStat> serverStats = emailServer.getConnectionStats();
                 for ( final EmailServer.ServerStat serverStat : EmailServer.ServerStat.values() )
                 {
                     final String name = serverStat.name() + "[" + emailServer.getId() + "]";

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

@@ -25,7 +25,7 @@ import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
@@ -46,7 +46,7 @@ public class HttpClientService implements PwmService
     private final Map<PwmHttpClientConfiguration, ThreadLocal<PwmHttpClient>> clients = new ConcurrentHashMap<>(  );
     private final Map<PwmHttpClient, Object> issuedClients = Collections.synchronizedMap( new WeakHashMap<>(  ) );
 
-    private final StatisticIntCounterMap<StatsKey> stats = new StatisticIntCounterMap<>( StatsKey.class );
+    private final StatisticCounterBundle<StatsKey> stats = new StatisticCounterBundle<>( StatsKey.class );
 
     enum StatsKey
     {

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

@@ -33,7 +33,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.PwmScheduler;
@@ -157,7 +157,7 @@ public class PwNotifyEngine
             }
 
             log( "starting job, beginning ldap search" );
-            final Queue<UserIdentity> workQueue = new ArrayDeque<>( UserPermissionTester.discoverMatchingUsers(
+            final Queue<UserIdentity> workQueue = new ArrayDeque<>( UserPermissionUtility.discoverMatchingUsers(
                     pwmApplication,
                     permissionList, SESSION_LABEL, settings.getMaxLdapSearchSize(),
                     settings.getSearchTimeout()

+ 6 - 3
server/src/main/java/password/pwm/svc/report/ReportService.java

@@ -35,7 +35,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.PwmService;
 import password.pwm.util.EventRateMeter;
 import password.pwm.util.PwmScheduler;
@@ -55,6 +55,7 @@ import java.math.BigDecimal;
 import java.time.Instant;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
@@ -377,11 +378,14 @@ public class ReportService implements PwmService
             resetJobStatus();
             clearWorkQueue();
 
-            final Queue<UserIdentity> memQueue = new ArrayDeque<>( UserPermissionTester.discoverMatchingUsers(
+            final List<UserIdentity> searchResults = new ArrayList<>( UserPermissionUtility.discoverMatchingUsers(
                     pwmApplication,
                     settings.getSearchFilter(), SessionLabel.REPORTING_SESSION_LABEL, settings.getMaxSearchSize(),
                     settings.getSearchTimeout()
             ) );
+            Collections.shuffle( searchResults );
+
+            final Queue<UserIdentity> memQueue = new ArrayDeque<>( searchResults );
 
             LOGGER.trace( SessionLabel.REPORTING_SESSION_LABEL, () -> "completed ldap search process (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
 
@@ -400,7 +404,6 @@ public class ReportService implements PwmService
                 }
             };
 
-
             writeUsersToLocalDBQueue( iterator );
         }
 

+ 2 - 1
server/src/main/java/password/pwm/svc/report/ReportSummaryData.java

@@ -27,6 +27,7 @@ import password.pwm.config.Configuration;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.i18n.Admin;
 import password.pwm.util.i18n.LocaleHelper;
+import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.Percent;
 import password.pwm.util.java.PwmNumberFormat;
 import password.pwm.util.java.TimeDuration;
@@ -310,7 +311,7 @@ public class ReportSummaryData
 
         returnCollection.add( builder.makeRow( "Field_Report_Sum_HaveResponses", this.hasResponses.get() ) );
         returnCollection.add( builder.makeRow( "Field_Report_Sum_HaveHelpdeskResponses", this.hasHelpdeskResponses.get() ) );
-        for ( final DataStorageMethod storageMethod : EnumSet.copyOf( this.getResponseStorage().keySet() ) )
+        for ( final DataStorageMethod storageMethod : JavaHelper.copiedEnumSet( this.getResponseStorage().keySet(), DataStorageMethod.class ) )
         {
             final int count = this.getResponseStorage().get( storageMethod );
             returnCollection.add( builder.makeRow( "Field_Report_Sum_StorageMethod", count, storageMethod.toString() ) );

+ 9 - 8
server/src/main/java/password/pwm/svc/stats/StatisticsBundle.java

@@ -20,7 +20,6 @@
 
 package password.pwm.svc.stats;
 
-import password.pwm.util.java.AtomicLoopLongIncrementer;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
@@ -30,17 +29,18 @@ import java.math.BigInteger;
 import java.util.EnumMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.LongAdder;
 
 public class StatisticsBundle
 {
-    private final Map<Statistic, AtomicLoopLongIncrementer> incrementerMap = new EnumMap<>( Statistic.class );
+    private final Map<Statistic, LongAdder> incrementerMap = new EnumMap<>( Statistic.class );
     private final Map<AvgStatistic, AverageBean> avgMap = new EnumMap<>( AvgStatistic.class );
 
     StatisticsBundle( )
     {
         for ( final Statistic statistic : Statistic.values() )
         {
-            incrementerMap.put( statistic, new AtomicLoopLongIncrementer() );
+            incrementerMap.put( statistic, new LongAdder() );
         }
         for ( final AvgStatistic avgStatistic : AvgStatistic.values() )
         {
@@ -54,7 +54,7 @@ public class StatisticsBundle
 
         for ( final Statistic statistic : Statistic.values() )
         {
-            final long currentValue = incrementerMap.get( statistic ).get();
+            final long currentValue = incrementerMap.get( statistic ).longValue();
             if ( currentValue > 0 )
             {
                 outputMap.put( statistic.name(), Long.toString( currentValue ) );
@@ -83,8 +83,9 @@ public class StatisticsBundle
             if ( !StringUtil.isEmpty( value ) )
             {
                 final long longValue = JavaHelper.silentParseLong( value, 0 );
-                final AtomicLoopLongIncrementer incrementer = AtomicLoopLongIncrementer.builder().initial( longValue ).build();
-                bundle.incrementerMap.put( loopStat, incrementer );
+                final LongAdder longAdder = new LongAdder();
+                longAdder.add( longValue );
+                bundle.incrementerMap.put( loopStat, longAdder );
             }
         }
 
@@ -103,7 +104,7 @@ public class StatisticsBundle
 
     void incrementValue( final Statistic statistic )
     {
-        incrementerMap.get( statistic ).next();
+        incrementerMap.get( statistic ).increment();
     }
 
     void updateAverageValue( final AvgStatistic statistic, final long timeDuration )
@@ -113,7 +114,7 @@ public class StatisticsBundle
 
     public String getStatistic( final Statistic statistic )
     {
-        return Long.toString( incrementerMap.get( statistic ).get() );
+        return Long.toString( incrementerMap.get( statistic ).longValue() );
     }
 
     public String getAvgStatistic( final AvgStatistic statistic )

+ 14 - 5
server/src/main/java/password/pwm/svc/wordlist/WordlistImporter.java

@@ -45,6 +45,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BooleanSupplier;
 
 /**
@@ -67,7 +68,7 @@ class WordlistImporter implements Runnable
     private ErrorInformation exitError;
     private Instant startTime = Instant.now();
     private long bytesSkipped;
-    private final Map<WordType, Long> seenWordTypes = new EnumMap<>( WordType.class );
+    private final Map<WordType, LongAdder> seenWordTypes = new EnumMap<>( WordType.class );
     private boolean completed;
 
     private enum DebugKey
@@ -162,7 +163,12 @@ class WordlistImporter implements Runnable
         checkWordlistSpaceRemaining();
 
         final long previousBytesRead = rootWordlist.readWordlistStatus().getBytes();
-        seenWordTypes.putAll( rootWordlist.readWordlistStatus().getWordTypes() );
+        for ( final Map.Entry<WordType, Long> entry : rootWordlist.readWordlistStatus().getWordTypes().entrySet() )
+        {
+            final LongAdder longAdder = new LongAdder();
+            longAdder.add( entry.getValue() );
+            seenWordTypes.put( entry.getKey(), longAdder );
+        }
 
         if ( previousBytesRead == 0 )
         {
@@ -254,7 +260,7 @@ class WordlistImporter implements Runnable
         }
 
         final WordType wordType = WordType.determineWordType( input );
-        seenWordTypes.compute( wordType, ( key, value ) -> value == null ? 0L : value + 1L );
+        seenWordTypes.getOrDefault( wordType, new LongAdder() ).increment();
 
         if ( wordType == WordType.RAW )
         {
@@ -318,7 +324,7 @@ class WordlistImporter implements Runnable
         final long wordlistSize = wordlistBucket.size();
 
         getLogger().info( () -> "population complete, added " + wordlistSize
-                + " total words in ", () -> TimeDuration.fromCurrent( startTime ) );
+                + " total words", () -> TimeDuration.fromCurrent( startTime ) );
 
         completed = true;
         writeCurrentWordlistStatus();
@@ -452,6 +458,9 @@ class WordlistImporter implements Runnable
 
     private void writeCurrentWordlistStatus()
     {
+        final Map<WordType, Long> outputWordTypeMap = new EnumMap<>( WordType.class );
+        seenWordTypes.forEach( ( key, value ) -> outputWordTypeMap.put( key, value.longValue() ) );
+
         final Instant now = Instant.now();
         rootWordlist.writeWordlistStatus( rootWordlist.readWordlistStatus().toBuilder()
                 .remoteInfo( wordlistSourceInfo )
@@ -460,7 +469,7 @@ class WordlistImporter implements Runnable
                 .checkDate( now )
                 .sourceType( sourceType )
                 .completed( completed )
-                .wordTypes( new EnumMap<>( seenWordTypes ) )
+                .wordTypes( outputWordTypeMap )
                 .bytes( zipFileReader.getByteCount() )
                 .build() );
     }

+ 16 - 17
server/src/main/java/password/pwm/util/java/StatisticIntCounterMap.java → server/src/main/java/password/pwm/util/java/StatisticCounterBundle.java

@@ -22,43 +22,42 @@ package password.pwm.util.java;
 
 import com.novell.ldapchai.util.StringHelper;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.EnumSet;
 import java.util.Map;
+import java.util.concurrent.atomic.LongAdder;
 import java.util.stream.Collectors;
 
-public class StatisticIntCounterMap<K extends Enum<K>>
+public class StatisticCounterBundle<K extends Enum<K>>
 {
-    private final Map<K, AtomicLoopIntIncrementer> statMap;
+    private final Class<K> keyType;
+    private final Map<K, LongAdder> statMap;
 
-    public StatisticIntCounterMap( final Class<K> keyType )
+    public StatisticCounterBundle( final Class<K> keyType )
     {
-        final EnumMap<K, AtomicLoopIntIncrementer> enumMap = new EnumMap<>( keyType );
-        for ( final K loopStatEnum : EnumSet.allOf( keyType ) )
-        {
-            enumMap.put( loopStatEnum, new AtomicLoopIntIncrementer() );
-        }
-        statMap = Collections.unmodifiableMap( enumMap );
+        this.keyType = keyType;
+        statMap = new EnumMap<>( keyType );
     }
 
     public void increment( final K stat )
     {
-        statMap.get( stat ).next();
+        statMap.computeIfAbsent( stat, k -> new LongAdder() ).increment();
     }
 
-    public int get( final K stat )
+    public long get( final K stat )
     {
-        return statMap.get( stat ).get();
+        final LongAdder longAdder = statMap.get( stat );
+        return longAdder == null ? 0 : longAdder.sum();
     }
 
     public Map<String, String> debugStats()
     {
-        return Collections.unmodifiableMap( statMap.entrySet()
-                .stream()
+        return Collections.unmodifiableMap( Arrays.stream( keyType.getEnumConstants() )
                 .collect( Collectors.toMap(
-                        ( entry ) -> entry.getKey().name(),
-                        ( entry ) -> String.valueOf( entry.getValue().get() ) ) ) );
+                        Enum::name,
+                        stat -> Long.toString( get( stat ) )
+                ) ) );
     }
 
     public String debugString()

+ 5 - 0
server/src/main/java/password/pwm/util/java/StringUtil.java

@@ -496,6 +496,11 @@ public abstract class StringUtil
         return StringUtils.isEmpty( input );
     }
 
+    public static boolean isTrimEmpty( final String input )
+    {
+        return isEmpty( input ) || input.trim().length() == 0;
+    }
+
     public static String defaultString( final String input, final String defaultStr )
     {
         return StringUtils.defaultString( input, defaultStr );

+ 1 - 16
server/src/main/java/password/pwm/util/localdb/LocalDBStoredQueue.java

@@ -737,8 +737,6 @@ public class LocalDBStoredQueue implements Queue<String>, Deque<String>
                 LOGGER.trace( () -> "loaded for db " + db + "; headPosition=" + headPosition + ", tailPosition=" + tailPosition + ", size=" + finalSize );
             }
 
-            checkSize();
-
             repair();
 
             debugOutput( "post init()" );
@@ -747,7 +745,7 @@ public class LocalDBStoredQueue implements Queue<String>, Deque<String>
         private boolean checkVersion( ) throws LocalDBException
         {
             final String storedVersion = localDB.get( db, KEY_VERSION );
-            if ( storedVersion == null || !VALUE_VERSION.equals( storedVersion ) )
+            if ( !Objects.equals( storedVersion, VALUE_VERSION ) )
             {
                 LOGGER.warn( () -> "values in db " + db + " use an outdated format, the stored events will be purged!" );
                 return false;
@@ -1063,19 +1061,6 @@ public class LocalDBStoredQueue implements Queue<String>, Deque<String>
             LOGGER.trace( debugOutput );
         }
 
-        private void checkSize()
-            throws LocalDBException
-        {
-            final long dbSize = localDB.size( db );
-            final long positionDistance = tailPosition.distanceToHead( headPosition );
-
-            // +3 for header/tail position and version keys.
-            if ( dbSize != positionDistance + 3 )
-            {
-                LOGGER.warn( () -> "dbSize=" + dbSize + " and positionDistance=" + positionDistance + " stored Queue is corrupted" );
-            }
-        }
-
         private void repair( ) throws LocalDBException
         {
             lock.writeLock().lock();

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

@@ -33,7 +33,7 @@ import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.MovingAverage;
-import password.pwm.util.java.StatisticIntCounterMap;
+import password.pwm.util.java.StatisticCounterBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -79,7 +79,7 @@ public final class WorkQueueProcessor<W extends Serializable>
     private final MovingAverage avgLagTime = new MovingAverage( TimeDuration.MINUTE );
     private final EventRateMeter sendRate = new EventRateMeter( TimeDuration.MINUTE );
 
-    private final StatisticIntCounterMap<WorkQueueStat> workQueueStats = new StatisticIntCounterMap<>( WorkQueueStat.class );
+    private final StatisticCounterBundle<WorkQueueStat> workQueueStats = new StatisticCounterBundle<>( WorkQueueStat.class );
 
     enum WorkQueueStat
     {

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

@@ -51,7 +51,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.wordlist.WordlistService;
 import password.pwm.util.java.JavaHelper;
@@ -256,7 +256,7 @@ public class CrService implements PwmService
                 LOGGER.debug( sessionLabel, () -> "testing challenge profiles '" + profile + "'" );
                 try
                 {
-                    final boolean match = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatch );
+                    final boolean match = UserPermissionUtility.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatch );
                     if ( match )
                     {
                         return profile;
@@ -611,13 +611,13 @@ public class CrService implements PwmService
             return false;
         }
 
-        if ( !UserPermissionTester.testUserPermission( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_SETUP_RESPONSE ) ) )
+        if ( !UserPermissionUtility.testUserPermission( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_SETUP_RESPONSE ) ) )
         {
             LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " does not have permission to setup responses" );
             return false;
         }
 
-        if ( !UserPermissionTester.testUserPermission( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_CHECK_RESPONSES ) ) )
+        if ( !UserPermissionUtility.testUserPermission( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_CHECK_RESPONSES ) ) )
         {
             LOGGER.debug( pwmSession, () -> "checkIfResponseConfigNeeded: " + userIdentity + " is not eligible for checkIfResponseConfigNeeded due to query match" );
             return false;

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

@@ -67,7 +67,7 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.LdapOperationsHelper;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.auth.AuthenticationType;
 import password.pwm.ldap.auth.PwmAuthenticationSource;
@@ -981,7 +981,7 @@ public class PasswordUtility
             LOGGER.debug( pwmSession, () -> "testing password policy profile '" + profile + "'" );
             try
             {
-                final boolean match = UserPermissionTester.testUserPermission( pwmApplication, pwmSession, userIdentity, userPermissions );
+                final boolean match = UserPermissionUtility.testUserPermission( pwmApplication, pwmSession, userIdentity, userPermissions );
                 if ( match )
                 {
                     return loopPolicy;

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

@@ -35,7 +35,7 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.auth.AuthenticationResult;
 import password.pwm.ldap.auth.SimpleLdapAuthenticator;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.util.BasicAuthInfo;
 import password.pwm.util.java.JavaHelper;
@@ -97,7 +97,7 @@ public class RestAuthenticationProcessor
             {
                 {
                     final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission( PwmSetting.WEBSERVICES_QUERY_MATCH );
-                    final boolean result = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission );
+                    final boolean result = UserPermissionUtility.testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission );
                     if ( !result )
                     {
                         final String errorMsg = "user does not have webservice permission due to setting "
@@ -109,7 +109,7 @@ public class RestAuthenticationProcessor
                 final boolean thirdParty;
                 {
                     final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission( PwmSetting.WEBSERVICES_THIRDPARTY_QUERY_MATCH );
-                    thirdParty = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission );
+                    thirdParty = UserPermissionUtility.testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission );
                 }
 
                 final ChaiProvider chaiProvider = authenticateUser( userIdentity );

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

@@ -39,7 +39,7 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.servlet.updateprofile.UpdateProfileUtil;
 import password.pwm.i18n.Message;
-import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionUtility;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
 import password.pwm.svc.stats.Statistic;
@@ -211,7 +211,7 @@ public class RestProfileServer extends RestServlet
 
         {
             final List<UserPermission> userPermission = updateProfileProfile.readSettingAsUserPermission( PwmSetting.UPDATE_PROFILE_QUERY_MATCH );
-            final boolean result = UserPermissionTester.testUserPermission(
+            final boolean result = UserPermissionUtility.testUserPermission(
                     restRequest.getPwmApplication(),
                     restRequest.getSessionLabel(),
                     targetUserIdentity.getUserIdentity(),

+ 2 - 1
server/src/main/resources/password/pwm/AppProperty.properties

@@ -73,7 +73,8 @@ config.newuser.passwordPolicyCacheMS=3600000
 config.theme=pwm
 config.enableJbCryptPwLibrary=true
 configEditor.blockOldIE=true
-configEditor.queryFilter.testLimit=5000
+configEditor.userPermission.matchResultsLimit=5000
+configEditor.userPermission.matchTimeoutSeconds=10
 configEditor.idleTimeoutSeconds=900
 configGuide.idleTimeoutSeconds=3600
 configManager.zipDebug.maxLogBytes=50000000

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

@@ -544,6 +544,8 @@
     </setting>
     <setting hidden="false" key="pwmAdmin.queryMatch" level="0" required="true">
         <ldapPermission actor="proxy" access="read"/>
+        <example>cn=admin,ou=users,o=example</example>
+        <example template="AD">CN=Administrator,CN=users,DC=site,DC=example,DC=net</example>
         <default>
             <value/>
         </default>

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

@@ -25,8 +25,8 @@ ldap_admin_title_proxy_dn=Proxy DN
 ldap_admin_title_proxy_pw=Proxy Password
 ldap_cert_description=The following are the LDAP server certificates read from the server at <code>%1%</code>. Please verify these certificates match your LDAP server.
 ldap_context_description=Please enter the top level container of your LDAP directory that contains users. This sets the top level LDAP container where an LDAP sub-tree search is performed to find user entries. If you need to enter multiple containers, you can add them after this guide completes.  Authentication to @PwmAppName@ is permitted only for users that are contained within the configured context values.
-ldap_context_admin_title=Administrators Group
-ldap_context_admin_description=<p>A group in your LDAP directory will be used to control administrative access to @PwmAppName@.</p><p>Please enter the LDAP distinguished name (DN) of the group to use to control administrative access.  Each member of this group will have administrative access to this application.</p><p>As with other users, administrative users must be contained within the previously configured LDAP Login Root Context.
+ldap_context_admin_title=Administrator User
+ldap_context_admin_description=<p>A user in your LDAP directory will be used to control administrative access to @PwmAppName@.</p><p>Please enter the LDAP distinguished name (DN) of the user to use to control administrative access.</p><p>As with all users, <b>administrative users must be contained within the previously configured LDAP Login Root Context ("%1%")</b>.</p><p>After the configuration guide is complete, you can assign additional users as administrators.</p>
 ldap_server_description=Enter the connection information for your LDAP server.  After the configuration guide is completed you can enter additional servers.  Enter the real address of your LDAP server; do not use a virtual address or proxy server address.
 ldap_server_title=LDAP Server
 ldap_server_title_hostname=Hostname

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

@@ -61,7 +61,9 @@ HealthMessage_Config_MissingProxyDN=Missing proxy user DN for profile %1%
 HealthMessage_Config_MissingProxyPassword=Missing proxy user password for profile %1%
 HealthMessage_Config_PasswordPolicyProblem=Password policy '%1%' configuration anomaly: %2%
 HealthMessage_Config_UserPermissionValidity=User Permission configuration for setting %1% issue: %2%.  This may cause unexpected issues.
+HealthMessage_Config_SettingIssue=Setting %1% has issue: %2%.
 HealthMessage_Config_DNValueValidity=LDAP DN configuration setting %1% issue: %2%.  This may cause unexpected issues.
+HealthMessage_Config_ProfileValueValidity=LDAP Profile for setting %1% has invalid value '%2%'.  This may cause unexpected issues.
 HealthMessage_Config_Certificate=Certificate for setting %1% issue: %2%
 HealthMessage_Config_InvalidSendMethod=The send method '%1%' is no longer available for setting %2%.  Please modify the configuration to use an alternate value.
 HealthMessage_Config_DeprecatedJSForm=The javascript form option in the form setting %1% has been deprecated and will be removed in a future version.  Use the setting %2% instead.

+ 2 - 2
webapp/src/main/webapp/WEB-INF/jsp/configguide-end.jsp

@@ -107,10 +107,10 @@
                         </td>
                     </tr>
                     <tr>
-                        <td><b>Administrator Group DN</b>
+                        <td><b>Administrator User DN</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_USER))%>
                         </td>
                     </tr>
                     <tr>

+ 4 - 9
webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_admins.jsp

@@ -49,21 +49,16 @@
                     <pwm:display key="ldap_context_admin_title" bundle="ConfigGuide"/>
                 </div>
                 <div class="setting_body">
-                    <pwm:display key="ldap_context_admin_description" bundle="ConfigGuide"/>
+                    <pwm:display key="ldap_context_admin_description" bundle="ConfigGuide" value1="<%=configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_CONTEXT)%>"/>
                     <div class="setting_item">
-                        Example: <code><%=PwmSetting.QUERY_MATCH_PWM_ADMIN.getExample(ConfigGuideForm.generateStoredConfig(configGuideBean).getTemplateSet())%></code>
                         <br/><br/>
-                        <b>Administrator Group DN</b>
+                        <b>Administrator User DN</b>
                         <br/>
-                        <input style="width:400px;" class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>" name="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP))%>" <pwm:autofocus/> required/>
+                        <input style="width:400px;" class="configStringInput" id="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_USER%>" name="<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_USER%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideFormField.PARAM_LDAP_ADMIN_USER))%>" <pwm:autofocus/> required/>
                         <button type="button" class="btn" id="button-browse-adminGroup">
                             <span class="btn-icon pwm-icon pwm-icon-sitemap"></span>
                             <pwm:display key="Button_Browse"/>
                         </button>
-                        <button type="button" id="button-viewAdminMatches" class="btn">
-                            <span class="btn-icon pwm-icon pwm-icon-eye"></span>
-                            View Group Members
-                        </button>
                     </div>
                 </div>
             </div>
@@ -119,7 +114,7 @@
 
             PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){
                 UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>').value = value;
+                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_USER%>').value = value;
                     handleFormActivity();
                 })
             });

+ 0 - 6
webapp/src/main/webapp/WEB-INF/jsp/configguide-ldap_context.jsp

@@ -105,12 +105,6 @@
                     handleFormActivity();
                 })
             });
-            PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){
-                UILibrary.editLdapDN(function(value){
-                    PWM_MAIN.getObject('<%=ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP%>').value = value;
-                    handleFormActivity();
-                })
-            });
         });
 
         function checkIfNextEnabled() {

+ 1 - 1
webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js

@@ -204,7 +204,7 @@ UserPermissionHandler.addRowHandlers = function( resultValue, keyName, rowKey) {
             PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ldapProfileID;
             PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value;
             UserPermissionHandler.write(keyName,true);
-        }, {currentDN: currentBaseValue});
+        }, {currentDN: currentBaseValue, profile: currentProfile});
     };
     if (currentBaseValue && currentBaseValue.length > 0) {
         UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue);