Ver Fonte

add ldapuser and alluser type permissions

Jason Rivard há 4 anos atrás
pai
commit
cf5f4e2eee
92 ficheiros alterados com 1668 adições e 1034 exclusões
  1. 1 0
      pom.xml
  2. 1 1
      server/src/main/java/password/pwm/AppProperty.java
  3. 20 6
      server/src/main/java/password/pwm/PwmApplication.java
  4. 4 3
      server/src/main/java/password/pwm/PwmConstants.java
  5. 1 1
      server/src/main/java/password/pwm/PwmEnvironment.java
  6. 4 4
      server/src/main/java/password/pwm/config/PwmSettingXml.java
  7. 20 9
      server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java
  8. 2 2
      server/src/main/java/password/pwm/config/profile/ProfileUtility.java
  9. 2 2
      server/src/main/java/password/pwm/config/stored/ConfigurationCleaner.java
  10. 1 1
      server/src/main/java/password/pwm/config/stored/ConfigurationReader.java
  11. 31 54
      server/src/main/java/password/pwm/config/value/UserPermissionValue.java
  12. 31 10
      server/src/main/java/password/pwm/config/value/data/UserPermission.java
  13. 1 1
      server/src/main/java/password/pwm/health/HealthMonitor.java
  14. 17 1
      server/src/main/java/password/pwm/health/LDAPHealthChecker.java
  15. 5 3
      server/src/main/java/password/pwm/http/HttpMethod.java
  16. 2 1
      server/src/main/java/password/pwm/http/PwmHttpRequestWrapper.java
  17. 3 3
      server/src/main/java/password/pwm/http/PwmURL.java
  18. 2 2
      server/src/main/java/password/pwm/http/SessionManager.java
  19. 16 6
      server/src/main/java/password/pwm/http/servlet/ShortcutServlet.java
  20. 3 3
      server/src/main/java/password/pwm/http/servlet/admin/AppDashboardData.java
  21. 2 2
      server/src/main/java/password/pwm/http/servlet/admin/UserDebugDataReader.java
  22. 1 56
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServlet.java
  23. 65 0
      server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java
  24. 2 1
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideForm.java
  25. 1 2
      server/src/main/java/password/pwm/http/servlet/configguide/ConfigGuideServlet.java
  26. 12 5
      server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServletUtil.java
  27. 10 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java
  28. 3 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchService.java
  29. 2 2
      server/src/main/java/password/pwm/http/servlet/peoplesearch/PhotoDataReader.java
  30. 2 4
      server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java
  31. 4 4
      server/src/main/java/password/pwm/ldap/LdapConnectionService.java
  32. 0 54
      server/src/main/java/password/pwm/ldap/LdapOperationsHelper.java
  33. 0 297
      server/src/main/java/password/pwm/ldap/LdapPermissionTester.java
  34. 58 0
      server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java
  35. 106 0
      server/src/main/java/password/pwm/ldap/permission/LdapGroupTypeHelper.java
  36. 155 0
      server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java
  37. 91 0
      server/src/main/java/password/pwm/ldap/permission/LdapUserDNTypeHelper.java
  38. 48 0
      server/src/main/java/password/pwm/ldap/permission/PermissionTypeHelper.java
  39. 205 0
      server/src/main/java/password/pwm/ldap/permission/UserPermissionTester.java
  40. 56 0
      server/src/main/java/password/pwm/ldap/permission/UserPermissionType.java
  41. 2 47
      server/src/main/java/password/pwm/ldap/search/SearchConfiguration.java
  42. 3 3
      server/src/main/java/password/pwm/ldap/search/UserSearchEngine.java
  43. 11 13
      server/src/main/java/password/pwm/svc/PwmService.java
  44. 2 2
      server/src/main/java/password/pwm/svc/cache/CacheService.java
  45. 9 8
      server/src/main/java/password/pwm/svc/email/EmailService.java
  46. 3 3
      server/src/main/java/password/pwm/svc/event/AuditEvent.java
  47. 2 2
      server/src/main/java/password/pwm/svc/event/AuditService.java
  48. 4 20
      server/src/main/java/password/pwm/svc/event/AuditVault.java
  49. 9 11
      server/src/main/java/password/pwm/svc/httpclient/HttpClientService.java
  50. 6 6
      server/src/main/java/password/pwm/svc/httpclient/PwmHttpClientRequest.java
  51. 2 2
      server/src/main/java/password/pwm/svc/intruder/IntruderManager.java
  52. 2 2
      server/src/main/java/password/pwm/svc/node/NodeInfo.java
  53. 4 1
      server/src/main/java/password/pwm/svc/node/NodeService.java
  54. 9 8
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyEngine.java
  55. 1 1
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifyService.java
  56. 2 0
      server/src/main/java/password/pwm/svc/pwnotify/PwNotifySettings.java
  57. 24 9
      server/src/main/java/password/pwm/svc/report/ReportService.java
  58. 4 0
      server/src/main/java/password/pwm/svc/report/ReportSettings.java
  59. 1 1
      server/src/main/java/password/pwm/svc/report/UserCacheService.java
  60. 1 2
      server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java
  61. 3 3
      server/src/main/java/password/pwm/svc/stats/StatisticsManager.java
  62. 1 1
      server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  63. 0 1
      server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java
  64. 17 3
      server/src/main/java/password/pwm/svc/token/TokenService.java
  65. 7 6
      server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java
  66. 2 3
      server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java
  67. 1 1
      server/src/main/java/password/pwm/util/FormMap.java
  68. 2 1
      server/src/main/java/password/pwm/util/LDAPPermissionCalculator.java
  69. 2 1
      server/src/main/java/password/pwm/util/PropertyConfigurationImporter.java
  70. 5 1
      server/src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java
  71. 7 5
      server/src/main/java/password/pwm/util/db/DatabaseService.java
  72. 19 0
      server/src/main/java/password/pwm/util/java/StringUtil.java
  73. 5 2
      server/src/main/java/password/pwm/util/localdb/LocalDBService.java
  74. 2 2
      server/src/main/java/password/pwm/util/localdb/WorkQueueProcessor.java
  75. 4 3
      server/src/main/java/password/pwm/util/logging/LocalDBLogger.java
  76. 4 4
      server/src/main/java/password/pwm/util/operations/ActionExecutor.java
  77. 5 5
      server/src/main/java/password/pwm/util/operations/CrService.java
  78. 1 1
      server/src/main/java/password/pwm/util/operations/OtpService.java
  79. 2 2
      server/src/main/java/password/pwm/util/password/PasswordUtility.java
  80. 7 6
      server/src/main/java/password/pwm/util/queue/SmsQueueManager.java
  81. 3 3
      server/src/main/java/password/pwm/ws/server/RestAuthenticationProcessor.java
  82. 2 2
      server/src/main/java/password/pwm/ws/server/rest/RestProfileServer.java
  83. 15 19
      server/src/main/resources/password/pwm/config/PwmSetting.xml
  84. 14 2
      server/src/main/resources/password/pwm/i18n/Config.properties
  85. 58 0
      server/src/test/java/password/pwm/config/stored/StoredConfigurationTest.java
  86. 1 0
      webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp
  87. 304 0
      webapp/src/main/webapp/public/resources/js/configeditor-settings-permissions.js
  88. 20 224
      webapp/src/main/webapp/public/resources/js/configeditor-settings.js
  89. 33 35
      webapp/src/main/webapp/public/resources/js/configeditor.js
  90. 3 3
      webapp/src/main/webapp/public/resources/js/configmanager.js
  91. 1 0
      webapp/src/main/webapp/public/resources/style.css
  92. 27 10
      webapp/src/main/webapp/public/resources/themes/pwm/configStyle.css

+ 1 - 0
pom.xml

@@ -325,6 +325,7 @@
                     <excludeFilterFile>${project.root.basedir}/build/spotbugs-exclude.xml</excludeFilterFile>
                     <includeTests>false</includeTests>
                     <effort>max</effort>
+                    <timeout>1200000</timeout>
                 </configuration>
                 <executions>
                     <execution>

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

@@ -313,7 +313,7 @@ public enum AppProperty
     RECAPTCHA_CLIENT_JS_URL                         ( "recaptcha.clientJsUrl" ),
     RECAPTCHA_CLIENT_IFRAME_URL                     ( "recaptcha.clientIframeUrl" ),
     RECAPTCHA_VALIDATE_URL                          ( "recaptcha.validateUrl" ),
-    REPORTING_LDAP_SEARCH_TIMEOUT                   ( "reporting.ldap.searchTimeoutMs" ),
+    REPORTING_LDAP_SEARCH_TIMEOUT_MS                ( "reporting.ldap.searchTimeoutMs" ),
     REPORTING_LDAP_SEARCH_THREADS                   ( "reporting.ldap.searchThreads" ),
     REPORTING_MAX_REPORT_AGE_SECONDS                ( "reporting.maxReportAgeSeconds" ),
     SECURITY_STRIP_INLINE_JAVASCRIPT                ( "security.html.stripInlineJavascript" ),

+ 20 - 6
server/src/main/java/password/pwm/PwmApplication.java

@@ -35,6 +35,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthMonitor;
+import password.pwm.http.servlet.configeditor.ConfigEditorServletUtils;
 import password.pwm.http.servlet.peoplesearch.PeopleSearchService;
 import password.pwm.http.servlet.resource.ResourceServletService;
 import password.pwm.http.state.SessionStateService;
@@ -221,10 +222,13 @@ public class PwmApplication
             }
         }
 
-        LOGGER.info( () -> "initializing, application mode=" + getApplicationMode()
-                + ", applicationPath=" + ( pwmEnvironment.getApplicationPath() == null ? "null" : pwmEnvironment.getApplicationPath().getAbsolutePath() )
-                + ", configFile=" + ( pwmEnvironment.getConfigurationFile() == null ? "null" : pwmEnvironment.getConfigurationFile().getAbsolutePath() )
-        );
+        if ( getApplicationMode() != PwmApplicationMode.READ_ONLY )
+        {
+            LOGGER.info( () -> "initializing, application mode=" + getApplicationMode()
+                    + ", applicationPath=" + ( pwmEnvironment.getApplicationPath() == null ? "null" : pwmEnvironment.getApplicationPath().getAbsolutePath() )
+                    + ", configFile=" + ( pwmEnvironment.getConfigurationFile() == null ? "null" : pwmEnvironment.getConfigurationFile().getAbsolutePath() )
+            );
+        }
 
         if ( !pwmEnvironment.isInternalRuntimeInstance() )
         {
@@ -384,6 +388,16 @@ public class PwmApplication
             LOGGER.debug( () -> "error initializing UserAgentUtils: " + e.getMessage() );
         }
 
+        try
+        {
+            ConfigEditorServletUtils.generateSettingData( this, this.getConfig().getStoredConfiguration(), null, PwmConstants.DEFAULT_LOCALE );
+        }
+        catch ( final Exception e )
+        {
+            LOGGER.debug( () -> "error initializing generateSettingData: " + e.getMessage() );
+        }
+
+
         {
             final ExecutorService executorService = PwmScheduler.makeSingleThreadExecutorService( this, PwmApplication.class );
             pwmScheduler.scheduleDailyZuluZeroStartJob( new DailySummaryJob( this ), executorService, TimeDuration.ZERO );
@@ -707,7 +721,7 @@ public class PwmApplication
             newInstanceID = Long.toHexString( pwmRandom.nextLong() ).toUpperCase();
 
             final String finalInstanceID = newInstanceID;
-            LOGGER.info( () -> "generated new random instanceID " + finalInstanceID );
+            LOGGER.debug( () -> "generated new random instanceID " + finalInstanceID );
 
             if ( localDB != null )
             {
@@ -929,7 +943,7 @@ public class PwmApplication
     {
         if ( localDB == null || localDB.status() != LocalDB.Status.OPEN )
         {
-            LOGGER.error( () -> "error retrieving key '" + appAttribute.getKey() + "', localDB unavailable: " );
+            LOGGER.debug( () -> "error retrieving key '" + appAttribute.getKey() + "', localDB unavailable: " );
             return null;
         }
 

+ 4 - 3
server/src/main/java/password/pwm/PwmConstants.java

@@ -22,13 +22,11 @@ package password.pwm;
 
 import com.novell.ldapchai.ChaiConstant;
 import org.apache.commons.csv.CSVFormat;
-import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.StringUtil;
 
 import java.io.InputStream;
 import java.net.URL;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -113,10 +111,11 @@ public abstract class PwmConstants
 
     public static final String LOG_REMOVED_VALUE_REPLACEMENT = readPwmConstantsBundle( "log.removedValue" );
 
-    public static final Collection<Locale> INCLUDED_LOCALES;
+    public static final Collection<Locale> INCLUDED_LOCALES = Collections.emptyList();
 
     static
     {
+        /*
         final List<Locale> localeList = new ArrayList<>();
         final String inputString = readPwmConstantsBundle( "includedLocales" );
         final List<String> inputList = JsonUtil.deserializeStringList( inputString );
@@ -125,6 +124,8 @@ public abstract class PwmConstants
             localeList.add( new Locale( localeKey ) );
         }
         INCLUDED_LOCALES = Collections.unmodifiableCollection( localeList );
+
+         */
     }
 
     public static final String URL_JSP_CONFIG_GUIDE = "WEB-INF/jsp/configguide-%1%.jsp";

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

@@ -173,7 +173,7 @@ public class PwmEnvironment
             throws PwmUnrecoverableException
     {
         return this.toBuilder()
-                .applicationMode( PwmApplicationMode.NEW )
+                .applicationMode( PwmApplicationMode.READ_ONLY )
                 .internalRuntimeInstance( true )
                 .configurationFile( null )
                 .config( configuration )

+ 4 - 4
server/src/main/java/password/pwm/config/PwmSettingXml.java

@@ -68,7 +68,7 @@ public class PwmSettingXml
 
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmSettingXml.class );
 
-    private static LazySoftReference<XmlDocument> xmlDocCache = new LazySoftReference<>( () -> readXml() );
+    private static final LazySoftReference<XmlDocument> XML_DOC_CACHE = new LazySoftReference<>( () -> readXml() );
     private static final AtomicInteger LOAD_COUNTER = new AtomicInteger( 0 );
 
     private static XmlDocument readXml( )
@@ -107,21 +107,21 @@ public class PwmSettingXml
     static XmlElement readSettingXml( final PwmSetting setting )
     {
         final String expression = "/settings/setting[@key=\"" + setting.getKey() + "\"]";
-        return xmlDocCache.get().evaluateXpathToElement( expression )
+        return XML_DOC_CACHE.get().evaluateXpathToElement( expression )
                 .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing setting for key '" + setting.getKey() + "'" ) );
     }
 
     static XmlElement readCategoryXml( final PwmSettingCategory category )
     {
         final String expression = "/settings/category[@key=\"" + category.toString() + "\"]";
-        return xmlDocCache.get().evaluateXpathToElement( expression )
+        return XML_DOC_CACHE.get().evaluateXpathToElement( expression )
                 .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing category for key '" + category.getKey() + "'" ) );
     }
 
     static XmlElement readTemplateXml( final PwmSettingTemplate template )
     {
         final String expression = "/settings/template[@key=\"" + template.toString() + "\"]";
-        return xmlDocCache.get().evaluateXpathToElement( expression )
+        return XML_DOC_CACHE.get().evaluateXpathToElement( expression )
                 .orElseThrow( () -> new IllegalStateException( "PwmSetting.xml is missing template for key '" + template.toString() + "'" ) );
     }
 

+ 20 - 9
server/src/main/java/password/pwm/config/function/UserMatchViewerFunction.java

@@ -40,7 +40,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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -86,7 +87,7 @@ public class UserMatchViewerFunction implements SettingUIFunction
         return userMatchViewerResults;
     }
 
-    public Collection<UserIdentity> discoverMatchingUsers(
+    public List<UserIdentity> discoverMatchingUsers(
             final PwmApplication pwmApplication,
             final int maxResultSize,
             final StoredConfiguration storedConfiguration,
@@ -99,26 +100,36 @@ 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 );
+
+        final TimeDuration maxSearchTime = TimeDuration.SECONDS_10;
+        return UserPermissionTester.discoverMatchingUsers( tempApplication, permissions, SessionLabel.SYSTEM_LABEL, maxResultSize, maxSearchTime );
+    }
+
+    private static void validateUserPermissionLdapValues(
+            final PwmApplication pwmApplication,
+            final List<UserPermission> permissions
+    )
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
         for ( final UserPermission userPermission : permissions )
         {
-            if ( userPermission.getType() == UserPermission.Type.ldapQuery )
+            if ( userPermission.getType() == UserPermissionType.ldapQuery )
             {
                 if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty() )
                 {
-                    testIfLdapDNIsValid( tempApplication, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
+                    testIfLdapDNIsValid( pwmApplication, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
                 }
             }
-            else if ( userPermission.getType() == UserPermission.Type.ldapGroup )
+            else if ( userPermission.getType() == UserPermissionType.ldapGroup )
             {
-                testIfLdapDNIsValid( tempApplication, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
+                testIfLdapDNIsValid( pwmApplication, userPermission.getLdapBase(), userPermission.getLdapProfileID() );
             }
         }
-
-        return LdapPermissionTester.discoverMatchingUsers( tempApplication, maxResultSize, permissions, SessionLabel.SYSTEM_LABEL ).keySet();
     }
 
 
-    private void testIfLdapDNIsValid( final PwmApplication pwmApplication, final String baseDN, final String profileID )
+    private static void testIfLdapDNIsValid( final PwmApplication pwmApplication, final String baseDN, final String profileID )
             throws PwmOperationalException, PwmUnrecoverableException
     {
         final Set<String> profileIDsToTest = new LinkedHashSet<>();

+ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, queryMatches );
+            final boolean match = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatches );
             if ( match )
             {
                 return Optional.of( profile.getIdentifier() );

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

@@ -88,7 +88,7 @@ class ConfigurationCleaner
                             + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY.getKey() + "/" + profileID
                             + " to replacement setting "
                             + PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY_LEVEL + ", value="
-                            + ValueTypeConverter.valueToBoolean( value ) );
+                            + ValueTypeConverter.valueToString( value ) );
                     final Optional<ValueMetaData> valueMetaData = oldConfig.readMetaData(
                             StoredConfigItemKey.fromSetting( PwmSetting.PASSWORD_POLICY_AD_COMPLEXITY, profileID ) );
                     final UserIdentity userIdentity = valueMetaData.map( ValueMetaData::getUserIdentity ).orElse( null );
@@ -121,7 +121,7 @@ class ConfigurationCleaner
                             + PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE ) + "/" + profileID
                             + " to replacement setting " + PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS.toMenuLocationDebug( profileID, PwmConstants.DEFAULT_LOCALE )
                             + ", value="
-                            + ValueTypeConverter.valueToBoolean( value ) );
+                            + ValueTypeConverter.valueToString( value ) );
                     modifier.writeSetting( PwmSetting.RECOVERY_MINIMUM_PASSWORD_LIFETIME_OPTIONS, profileID, value, newActor );
                 }
             }

+ 1 - 1
server/src/main/java/password/pwm/config/stored/ConfigurationReader.java

@@ -350,7 +350,7 @@ public class ConfigurationReader
             return "";
         }
 
-        return String.valueOf( file.lastModified() + String.valueOf( file.length() ) );
+        return file.lastModified() + "+" + file.length();
     }
 
     public ErrorInformation getConfigFileError( )

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

@@ -21,13 +21,14 @@
 package password.pwm.config.value;
 
 import com.google.gson.reflect.TypeToken;
-import org.apache.commons.lang3.StringUtils;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.stored.StoredConfigXmlSerializer;
 import password.pwm.config.stored.XmlOutputProcessData;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmOperationalException;
-import password.pwm.i18n.Display;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.XmlElement;
 import password.pwm.util.java.XmlFactory;
@@ -35,6 +36,7 @@ import password.pwm.util.secure.PwmSecurityKey;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 
@@ -46,7 +48,24 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
 
     public UserPermissionValue( final List<UserPermission> values )
     {
-        this.values = values == null ? Collections.emptyList() : Collections.unmodifiableList( values );
+       this.values = sanitizeList( values );
+    }
+
+    private List<UserPermission> sanitizeList( final List<UserPermission> permissions )
+    {
+        final List<UserPermission> tempList = new ArrayList<>();
+        if ( permissions != null )
+        {
+            tempList.addAll( permissions );
+        }
+
+        while ( tempList.contains( null ) )
+        {
+            tempList.remove( null );
+        }
+
+        Collections.sort( tempList );
+        return Collections.unmodifiableList( tempList );
     }
 
     public static StoredValueFactory factory( )
@@ -65,10 +84,6 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
                     {
                     } );
                     srcList = srcList == null ? Collections.emptyList() : srcList;
-                    while ( srcList.contains( null ) )
-                    {
-                        srcList.remove( null );
-                    }
                     return new UserPermissionValue( Collections.unmodifiableList( srcList ) );
                 }
             }
@@ -93,7 +108,7 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
                         else
                         {
                             values.add( UserPermission.builder()
-                                    .type( UserPermission.Type.ldapQuery )
+                                    .type( UserPermissionType.ldapQuery )
                                     .ldapQuery( value )
                                     .build() );
                         }
@@ -130,11 +145,11 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
         {
             try
             {
-                validateLdapSearchFilter( userPermission.getLdapQuery() );
+                 UserPermissionTester.validatePermissionSyntax( userPermission );
             }
-            catch ( final IllegalArgumentException e )
+            catch ( final PwmUnrecoverableException e )
             {
-                returnObj.add( e.getMessage() + " for filter " + userPermission.getLdapQuery() );
+                returnObj.add( e.getMessage() );
             }
         }
         return returnObj;
@@ -145,22 +160,6 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
         return needsXmlUpdate;
     }
 
-    private void validateLdapSearchFilter( final String filter )
-    {
-        if ( filter == null || filter.isEmpty() )
-        {
-            return;
-        }
-
-        final int leftParens = StringUtils.countMatches( filter, "(" );
-        final int rightParens = StringUtils.countMatches( filter, ")" );
-
-        if ( leftParens != rightParens )
-        {
-            throw new IllegalArgumentException( "unbalanced parentheses" );
-        }
-    }
-
     @Override
     public int currentSyntaxVersion( )
     {
@@ -172,34 +171,12 @@ public class UserPermissionValue extends AbstractValue implements StoredValue
         if ( values != null && !values.isEmpty() )
         {
             final StringBuilder sb = new StringBuilder();
-            int counter = 0;
-            for ( final UserPermission userPermission : values )
+            for ( final Iterator<UserPermission> iterator = values.iterator(); iterator.hasNext(); )
             {
-                sb.append( "UserPermission" );
-                if ( values.size() > 1 )
-                {
-                    sb.append( counter );
-                }
-                sb.append( "-" );
-                sb.append( userPermission.getType() == null ? UserPermission.Type.ldapQuery.toString() : userPermission.getType().toString() );
-                sb.append( ": [" );
-                sb.append( "Profile:" ).append(
-                        userPermission.getLdapProfileID() == null
-                                ? "All"
-                                : userPermission.getLdapProfileID()
-                );
-                sb.append( " Base:" ).append(
-                        userPermission.getLdapBase() == null
-                                ? Display.getLocalizedMessage( locale, Display.Value_NotApplicable, null )
-                                : userPermission.getLdapBase()
-                );
-                if ( userPermission.getLdapQuery() != null )
-                {
-                    sb.append( " Query:" ).append( userPermission.getLdapQuery() );
-                }
-                sb.append( "]" );
-                counter++;
-                if ( counter != values.size() )
+                final UserPermission userPermission = iterator.next();
+                sb.append( "UserPermission: " );
+                sb.append( userPermission.debugString() );
+                if ( iterator.hasNext() )
                 {
                     sb.append( "\n" );
                 }

+ 31 - 10
server/src/main/java/password/pwm/config/value/data/UserPermission.java

@@ -22,28 +22,49 @@ package password.pwm.config.value.data;
 
 import lombok.Builder;
 import lombok.Value;
+import org.jetbrains.annotations.NotNull;
+import password.pwm.ldap.permission.UserPermissionType;
+import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
 
 @Value
 @Builder
-public class UserPermission implements Serializable
+/**
+ * Represents a user permission configuration value.
+ */
+public class UserPermission implements Serializable, Comparable<UserPermission>
 {
-    public enum Type
-    {
-        ldapQuery,
-        ldapGroup,
-    }
-
     @Builder.Default
-    private Type type = Type.ldapQuery;
+    private UserPermissionType type = UserPermissionType.ldapQuery;
 
     private String ldapProfileID;
     private String ldapQuery;
     private String ldapBase;
 
-    public Type getType( )
+    public UserPermissionType getType( )
+    {
+        return type == null ? UserPermissionType.ldapQuery : type;
+    }
+
+    public String debugString()
+    {
+        return getType().getLabel()
+                +  ": [Profile: "
+                + ( StringUtil.isEmpty( getLdapProfileID() ) ?  "All" : '\'' + this.getLdapProfileID() + '\'' )
+                + ( StringUtil.isEmpty( getLdapBase() ) ?  "" : " " + getType().getBaseLabel() + ": " + this.getLdapBase() )
+                + ( StringUtil.isEmpty( getLdapQuery() ) ?  "" : " Filter: " + this.getLdapQuery() )
+                + "]";
+    }
+
+    @Override
+    public int compareTo( @NotNull final UserPermission o )
+    {
+        return makeComparisonString().compareTo( o.makeComparisonString() );
+    }
+
+    private String makeComparisonString()
     {
-        return type == null ? Type.ldapQuery : type;
+        return getType().ordinal() + "-" + getLdapProfileID() + "-" + getLdapBase() + "-" + getLdapQuery();
     }
 }

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

@@ -276,7 +276,7 @@ public class HealthMonitor implements PwmService
 
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.emptyList() );
+        return ServiceInfoBean.builder().build();
     }
 
     Map<HealthMonitorFlag, Serializable> getHealthProperties( )

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

@@ -980,7 +980,7 @@ public class LDAPHealthChecker implements HealthChecker
 
             final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                     .enableValueEscaping( false )
-                    .searchTimeout( warnDuration.asMillis() )
+                    .searchTimeout( warnDuration )
                     .username( healthUsername )
                     .build();
 
@@ -1045,6 +1045,22 @@ public class LDAPHealthChecker implements HealthChecker
         {
             switch ( userPermission.getType() )
             {
+                case ldapAllUsers:
+                    break;
+
+                case ldapUser:
+                {
+                    final String userDN = userPermission.getLdapBase();
+                    if ( userDN != null && !isExampleDN( userDN ) )
+                    {
+                        final Optional<String> errorMsg = validateDN( pwmApplication, userDN, ldapProfileID );
+                        errorMsg.ifPresent( s -> returnList.add( HealthRecord.forMessage(
+                                HealthMessage.Config_UserPermissionValidity,
+                                settingDebugName, "userDN: " + s ) ) );
+                    }
+                }
+                break;
+
                 case ldapGroup:
                 {
                     final String groupDN = userPermission.getLdapBase();

+ 5 - 3
server/src/main/java/password/pwm/http/HttpMethod.java

@@ -20,6 +20,8 @@
 
 package password.pwm.http;
 
+import java.util.Optional;
+
 public enum HttpMethod
 {
     POST( false, true ),
@@ -37,16 +39,16 @@ public enum HttpMethod
         this.idempotent = idempotent;
     }
 
-    public static HttpMethod fromString( final String input )
+    public static Optional<HttpMethod> fromString( final String input )
     {
         for ( final HttpMethod method : HttpMethod.values() )
         {
             if ( method.toString().equalsIgnoreCase( input ) )
             {
-                return method;
+                return Optional.of( method );
             }
         }
-        return null;
+        return Optional.empty();
     }
 
     public boolean isIdempotent( )

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

@@ -425,7 +425,8 @@ public class PwmHttpRequestWrapper
 
     public HttpMethod getMethod( )
     {
-        return HttpMethod.fromString( this.getHttpServletRequest().getMethod() );
+        return HttpMethod.fromString( this.getHttpServletRequest().getMethod() )
+                .orElseThrow( () -> new IllegalStateException( "http method not registered" ) );
     }
 
     public Configuration getConfig( )

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

@@ -41,8 +41,8 @@ public class PwmURL
 {
     private static final PwmLogger LOGGER = PwmLogger.forClass( PwmURL.class );
 
-    private URI uri;
-    private String contextPath;
+    private final URI uri;
+    private final String contextPath;
 
     public PwmURL(
             final URI uri,
@@ -67,7 +67,7 @@ public class PwmURL
      *
      * @param uri1 uri to compare
      * @param uri2 uri to compare
-     * @return true if scheama, host and port of uri1 and uri2 are equal.
+     * @return true if schema, host and port of uri1 and uri2 are equal.
      */
     public static boolean compareUriBase( final String uri1, final String uri2 )
     {

+ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
+            final boolean result = UserPermissionTester.testUserPermission( pwmApplication, pwmSession.getLabel(), pwmSession.getUserInfo().getUserIdentity(), userPermission );
             status = result ? Permission.PermissionStatus.GRANTED : Permission.PermissionStatus.DENIED;
             pwmSession.getUserSessionDataCacheBean().setPermission( permission, status );
 

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

@@ -24,8 +24,10 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.util.StringHelper;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.data.ShortcutItem;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.HttpMethod;
@@ -34,7 +36,8 @@ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.logging.PwmLogger;
@@ -182,11 +185,18 @@ public class ShortcutServlet extends AbstractPwmServlet
         {
             for ( final ShortcutItem item : configuredItems )
             {
-                final boolean queryMatch = LdapPermissionTester.testQueryMatch(
-                        pwmRequest.getPwmApplication(),
-                        pwmRequest.getLabel(),
+                final UserIdentity userIdentity = pwmRequest.getPwmSession().getUserInfo().getUserIdentity();
+
+                final UserPermission userPermission = UserPermission.builder()
+                        .type( UserPermissionType.ldapQuery )
+                        .ldapQuery( item.getLdapQuery() )
+                        .ldapBase( userIdentity.getLdapProfileID() )
+                        .build();
+
+                final boolean queryMatch = UserPermissionTester.testUserPermission(
+                        pwmRequest.commonValues(),
                         pwmRequest.getPwmSession().getUserInfo().getUserIdentity(),
-                        item.getLdapQuery()
+                        userPermission
                 );
 
                 if ( queryMatch )
@@ -203,7 +213,7 @@ public class ShortcutServlet extends AbstractPwmServlet
             final PwmRequest pwmRequest,
             final ShortcutsBean shortcutsBean
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException, IOException, ServletException
+            throws PwmUnrecoverableException,  IOException, ServletException
     {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();

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

@@ -156,7 +156,7 @@ public class AppDashboardData implements Serializable
                 ? "This node is the current master"
                 : "This node is not the current master";
         {
-            final Collection<DataStorageMethod> dataStorageMethods = pwmApplication.getClusterService().serviceInfo().getUsedStorageMethods();
+            final Collection<DataStorageMethod> dataStorageMethods = pwmApplication.getClusterService().serviceInfo().getStorageMethods();
             if ( !JavaHelper.isEmpty( dataStorageMethods ) )
             {
                 builder.nodeStorageMethod = dataStorageMethods.iterator().next();
@@ -259,9 +259,9 @@ public class AppDashboardData implements Serializable
             final PwmService.ServiceInfo serviceInfo = pwmService.serviceInfo();
             final Collection<DataStorageMethod> storageMethods = serviceInfo == null
                     ? Collections.emptyList()
-                    : serviceInfo.getUsedStorageMethods() == null
+                    : serviceInfo.getStorageMethods() == null
                     ? Collections.emptyList()
-                    : serviceInfo.getUsedStorageMethods();
+                    : serviceInfo.getStorageMethods();
 
             final Map<String, String> debugData = serviceInfo == null
                     ? Collections.emptyMap()

+ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions(
+                final boolean result = UserPermissionTester.testUserPermission(
                         pwmApplication,
                         sessionLabel,
                         userIdentity,

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

@@ -22,11 +22,9 @@ package password.pwm.http.servlet.configeditor;
 
 import com.novell.ldapchai.exception.ChaiUnavailableException;
 import password.pwm.AppProperty;
-import password.pwm.PwmApplication;
 import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
-import password.pwm.bean.SessionLabel;
 import password.pwm.bean.SmsItemBean;
 import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
@@ -34,7 +32,6 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
 import password.pwm.config.PwmSettingSyntax;
 import password.pwm.config.PwmSettingTemplate;
-import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.SettingUIFunction;
 import password.pwm.config.value.StoredValue;
 import password.pwm.config.profile.EmailServerProfile;
@@ -968,7 +965,7 @@ public class ConfigEditorServlet extends ControlledPwmServlet
             throws IOException, PwmUnrecoverableException
     {
         final ConfigManagerBean configManagerBean = getBean( pwmRequest );
-        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>( generateSettingData(
+        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>( ConfigEditorServletUtils.generateSettingData(
                 pwmRequest.getPwmApplication(),
                 configManagerBean.getStoredConfiguration(),
                 pwmRequest.getLabel(),
@@ -990,58 +987,6 @@ public class ConfigEditorServlet extends ControlledPwmServlet
         return ProcessStatus.Halt;
     }
 
-    public static Map<String, Object> generateSettingData(
-            final PwmApplication pwmApplication,
-            final StoredConfiguration storedConfiguration,
-            final SessionLabel sessionLabel,
-            final Locale locale
-
-    ) throws PwmUnrecoverableException
-    {
-        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
-        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific( pwmApplication, sessionLabel );
-        final PwmSettingTemplateSet template = storedConfiguration.getTemplateSet();
-
-        {
-            final LinkedHashMap<String, Object> settingMap = new LinkedHashMap<>();
-            for ( final PwmSetting setting : PwmSetting.values() )
-            {
-
-                settingMap.put( setting.getKey(), SettingInfo.forSetting( setting, template, macroMachine, locale ) );
-            }
-            returnMap.put( "settings", settingMap );
-        }
-        {
-            final LinkedHashMap<String, Object> categoryMap = new LinkedHashMap<>();
-            for ( final PwmSettingCategory category : PwmSettingCategory.values() )
-            {
-                categoryMap.put( category.getKey(), CategoryInfo.forCategory( category, macroMachine, locale ) );
-            }
-            returnMap.put( "categories", categoryMap );
-        }
-        {
-            final LinkedHashMap<String, Object> labelMap = new LinkedHashMap<>();
-            for ( final PwmLocaleBundle localeBundle : PwmLocaleBundle.values() )
-            {
-                final LocaleInfo localeInfo = new LocaleInfo();
-                localeInfo.description = localeBundle.getTheClass().getSimpleName();
-                localeInfo.key = localeBundle.toString();
-                localeInfo.adminOnly = localeBundle.isAdminOnly();
-                labelMap.put( localeBundle.getTheClass().getSimpleName(), localeInfo );
-            }
-            returnMap.put( "locales", labelMap );
-        }
-        {
-            final LinkedHashMap<String, Object> varMap = new LinkedHashMap<>();
-            varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ).toNativeObject() );
-            varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() );
-            varMap.put( "configurationNotes", storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ) );
-            returnMap.put( "var", varMap );
-        }
-        return Collections.unmodifiableMap( returnMap );
-
-    }
-
     @ActionHandler( action = "testMacro" )
     private ProcessStatus restTestMacro( final PwmRequest pwmRequest ) throws IOException, ServletException
     {

+ 65 - 0
server/src/main/java/password/pwm/http/servlet/configeditor/ConfigEditorServletUtils.java

@@ -20,9 +20,16 @@
 
 package password.pwm.http.servlet.configeditor;
 
+import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
 import password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.PwmSettingCategory;
+import password.pwm.config.PwmSettingTemplateSet;
+import password.pwm.config.stored.ConfigurationProperty;
 import password.pwm.config.stored.StoredConfigItemKey;
+import password.pwm.config.stored.StoredConfiguration;
 import password.pwm.config.stored.StoredConfigurationUtil;
 import password.pwm.config.value.FileValue;
 import password.pwm.error.ErrorInformation;
@@ -33,15 +40,18 @@ import password.pwm.health.ConfigurationChecker;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.bean.ConfigManagerBean;
+import password.pwm.i18n.PwmLocaleBundle;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.bean.HealthData;
 
 import javax.servlet.ServletException;
 import java.io.IOException;
 import java.time.Instant;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -164,4 +174,59 @@ public class ConfigEditorServletUtils
 
         return HealthData.builder().build();
     }
+
+    public static Map<String, Object> generateSettingData(
+            final PwmApplication pwmApplication,
+            final StoredConfiguration storedConfiguration,
+            final SessionLabel sessionLabel,
+            final Locale locale
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final Instant startTime = Instant.now();
+        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>();
+        final MacroMachine macroMachine = MacroMachine.forNonUserSpecific( pwmApplication, sessionLabel );
+        final PwmSettingTemplateSet template = storedConfiguration.getTemplateSet();
+
+        {
+            final LinkedHashMap<String, Object> settingMap = new LinkedHashMap<>();
+            for ( final PwmSetting setting : PwmSetting.values() )
+            {
+
+                settingMap.put( setting.getKey(), SettingInfo.forSetting( setting, template, macroMachine, locale ) );
+            }
+            returnMap.put( "settings", settingMap );
+        }
+        {
+            final LinkedHashMap<String, Object> categoryMap = new LinkedHashMap<>();
+            for ( final PwmSettingCategory category : PwmSettingCategory.values() )
+            {
+                categoryMap.put( category.getKey(), CategoryInfo.forCategory( category, macroMachine, locale ) );
+            }
+            returnMap.put( "categories", categoryMap );
+        }
+        {
+            final LinkedHashMap<String, Object> labelMap = new LinkedHashMap<>();
+            for ( final PwmLocaleBundle localeBundle : PwmLocaleBundle.values() )
+            {
+                final LocaleInfo localeInfo = new LocaleInfo();
+                localeInfo.description = localeBundle.getTheClass().getSimpleName();
+                localeInfo.key = localeBundle.toString();
+                localeInfo.adminOnly = localeBundle.isAdminOnly();
+                labelMap.put( localeBundle.getTheClass().getSimpleName(), localeInfo );
+            }
+            returnMap.put( "locales", labelMap );
+        }
+        {
+            final LinkedHashMap<String, Object> varMap = new LinkedHashMap<>();
+            varMap.put( "ldapProfileIds", storedConfiguration.readSetting( PwmSetting.LDAP_PROFILE_LIST, null ).toNativeObject() );
+            varMap.put( "currentTemplate", storedConfiguration.getTemplateSet() );
+            varMap.put( "configurationNotes", storedConfiguration.readConfigProperty( ConfigurationProperty.NOTES ) );
+            returnMap.put( "var", varMap );
+        }
+        LOGGER.trace( sessionLabel, () -> "generated settingData", () -> TimeDuration.fromCurrent( startTime ) );
+        return Collections.unmodifiableMap( returnMap );
+
+    }
 }

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

@@ -37,6 +37,7 @@ import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.bean.ConfigGuideBean;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.PasswordData;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
@@ -173,7 +174,7 @@ public class ConfigGuideForm
             // set admin query
             final String groupDN = formData.get( ConfigGuideFormField.PARAM_LDAP_ADMIN_GROUP );
             final List<UserPermission> userPermissions = Collections.singletonList( UserPermission.builder()
-                    .type( UserPermission.Type.ldapGroup )
+                    .type( UserPermissionType.ldapGroup )
                     .ldapBase( groupDN )
                     .build() );
             storedConfiguration.writeSetting( PwmSetting.QUERY_MATCH_PWM_ADMIN, null, new UserPermissionValue( userPermissions ), null );

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

@@ -60,7 +60,6 @@ import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ConfigGuideBean;
 import password.pwm.http.servlet.AbstractPwmServlet;
 import password.pwm.http.servlet.ControlledPwmServlet;
-import password.pwm.http.servlet.configeditor.ConfigEditorServlet;
 import password.pwm.http.servlet.configeditor.ConfigEditorServletUtils;
 import password.pwm.i18n.Message;
 import password.pwm.ldap.LdapBrowser;
@@ -670,7 +669,7 @@ public class ConfigGuideServlet extends ControlledPwmServlet
         final ConfigGuideBean configGuideBean = getBean( pwmRequest );
         final StoredConfiguration storedConfiguration = ConfigGuideForm.generateStoredConfig( configGuideBean );
 
-        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>( ConfigEditorServlet.generateSettingData(
+        final LinkedHashMap<String, Object> returnMap = new LinkedHashMap<>( ConfigEditorServletUtils.generateSettingData(
                 pwmRequest.getPwmApplication(),
                 storedConfiguration,
                 pwmRequest.getLabel(),

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

@@ -31,14 +31,16 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.config.profile.HelpdeskProfile;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
-import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.svc.event.AuditEvent;
 import password.pwm.svc.event.AuditRecordFactory;
 import password.pwm.svc.event.HelpdeskAuditRecord;
@@ -179,11 +181,16 @@ public class HelpdeskServletUtil
             filterString = filterString.replace( "**", "*" );
         }
 
-        final boolean match = LdapPermissionTester.testQueryMatch(
-                pwmRequest.getPwmApplication(),
-                pwmRequest.getLabel(),
+        final UserPermission userPermission = UserPermission.builder()
+                .type( UserPermissionType.ldapQuery )
+                .ldapQuery( filterString )
+                .ldapProfileID( userIdentity.getLdapProfileID() )
+                .build();
+
+        final boolean match = UserPermissionTester.testUserPermission(
+                pwmRequest.commonValues(),
                 userIdentity,
-                filterString
+                userPermission
         );
 
         if ( !match )

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

@@ -34,6 +34,7 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.PeopleSearchProfile;
 import password.pwm.config.value.data.FormConfiguration;
+import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -48,10 +49,11 @@ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 import password.pwm.ldap.PhotoDataBean;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
 import password.pwm.ldap.search.UserSearchResults;
@@ -615,7 +617,13 @@ class PeopleSearchDataReader
                 filterString = filterString.replace( "**", "*" );
             }
 
-            return LdapPermissionTester.testQueryMatch( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userIdentity, filterString );
+            final UserPermission userPermission = UserPermission.builder()
+                    .type( UserPermissionType.ldapQuery )
+                    .ldapQuery( filterString )
+                    .ldapProfileID( userIdentity.getLdapProfileID() )
+                    .build();
+
+            return UserPermissionTester.testUserPermission( pwmRequest.commonValues(), userIdentity, userPermission );
         };
 
         final boolean result = storeDataInCache( CacheIdentifier.checkIfViewable, userIdentity.toDelimitedKey(), Boolean.class, cacheLoader );

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

@@ -26,6 +26,7 @@ import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
 import password.pwm.util.PwmScheduler;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.ThreadFactory;
@@ -70,13 +71,13 @@ public class PeopleSearchService implements PwmService
     @Override
     public List<HealthRecord> healthCheck()
     {
-        return null;
+        return Collections.emptyList();
     }
 
     @Override
     public ServiceInfoBean serviceInfo()
     {
-        return null;
+        return ServiceInfoBean.builder().build();
     }
 
     public ThreadPoolExecutor getJobExecutor()

+ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userIdentity, permissions );
+        final boolean hasPermission = UserPermissionTester.testUserPermission( pwmRequest.getPwmApplication(), pwmRequest.getLabel(), userIdentity, permissions );
         if ( !hasPermission )
         {
             LOGGER.debug( pwmRequest, () -> "user " + userIdentity + " failed photo query filter, denying photo view ("

+ 2 - 4
server/src/main/java/password/pwm/http/tag/conditional/PwmIfOptions.java

@@ -20,14 +20,12 @@
 
 package password.pwm.http.tag.conditional;
 
-import lombok.AllArgsConstructor;
-import lombok.Getter;
+import lombok.Value;
 import password.pwm.Permission;
 import password.pwm.config.PwmSetting;
 import password.pwm.http.PwmRequestFlag;
 
-@Getter
-@AllArgsConstructor
+@Value
 class PwmIfOptions
 {
     private final boolean negate;

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

@@ -175,10 +175,10 @@ public class LdapConnectionService implements PwmService
         final Map<String, String> debugProperties = new LinkedHashMap<>();
         debugProperties.putAll( chaiProviderFactory.getGlobalStatistics() );
         debugProperties.putAll( connectionDebugInfo() );
-        return new ServiceInfoBean(
-                Collections.singletonList( DataStorageMethod.LDAP ),
-                Collections.unmodifiableMap( debugProperties )
-        );
+        return ServiceInfoBean.builder()
+                .storageMethod(  DataStorageMethod.LDAP )
+                .debugProperties( debugProperties )
+                .build();
     }
 
 

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

@@ -44,7 +44,6 @@ import password.pwm.config.PwmSetting;
 import password.pwm.config.option.AutoSetLdapUserLanguage;
 import password.pwm.config.profile.LdapProfile;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
@@ -59,7 +58,6 @@ import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.PasswordData;
 import password.pwm.util.i18n.LocaleHelper;
-import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.java.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
@@ -72,16 +70,13 @@ import java.io.IOException;
 import java.net.URLConnection;
 import java.security.cert.X509Certificate;
 import java.time.Instant;
-import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Queue;
 import java.util.Set;
 
 public class LdapOperationsHelper
@@ -854,55 +849,6 @@ public class LdapOperationsHelper
         return Collections.emptyMap();
     }
 
-    public static Iterator<UserIdentity> readUsersFromLdapForPermissions(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final List<UserPermission> permissionList,
-            final int maxResults
-    )
-            throws PwmUnrecoverableException, PwmOperationalException
-    {
-        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
-        final Queue<UserIdentity> resultSet = new ArrayDeque<>();
-        final long searchTimeoutMs = JavaHelper.silentParseLong(
-                pwmApplication.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT ),
-                30_000 );
-
-        for ( final UserPermission userPermission : permissionList )
-        {
-            if ( resultSet.size() < maxResults )
-            {
-                final SearchConfiguration searchConfiguration = SearchConfiguration.fromPermission( userPermission )
-                        .toBuilder()
-                        .searchTimeout( searchTimeoutMs )
-                        .build();
-                final Map<UserIdentity, Map<String, String>> searchResults = userSearchEngine.performMultiUserSearch(
-                        searchConfiguration,
-                        maxResults - resultSet.size(),
-                        Collections.emptyList(),
-                        sessionLabel
-
-                );
-                resultSet.addAll( searchResults.keySet() );
-            }
-        }
-
-        return new Iterator<UserIdentity>()
-        {
-            @Override
-            public boolean hasNext( )
-            {
-                return resultSet.peek() != null;
-            }
-
-            @Override
-            public UserIdentity next( )
-            {
-                return resultSet.poll();
-            }
-        };
-    }
-
     public static Instant readPasswordExpirationTime( final ChaiUser theUser )
     {
         try

+ 0 - 297
server/src/main/java/password/pwm/ldap/LdapPermissionTester.java

@@ -1,297 +0,0 @@
-/*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2020 The PWM Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package password.pwm.ldap;
-
-import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.exception.ChaiException;
-import com.novell.ldapchai.provider.SearchScope;
-import password.pwm.PwmApplication;
-import password.pwm.PwmConstants;
-import password.pwm.bean.SessionLabel;
-import password.pwm.bean.UserIdentity;
-import password.pwm.config.PwmSetting;
-import password.pwm.config.profile.LdapProfile;
-import password.pwm.config.value.data.UserPermission;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.search.SearchConfiguration;
-import password.pwm.ldap.search.UserSearchEngine;
-import password.pwm.util.java.TimeDuration;
-import password.pwm.util.logging.PwmLogger;
-
-import java.time.Instant;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-public class LdapPermissionTester
-{
-    private static final PwmLogger LOGGER = PwmLogger.forClass( LdapPermissionTester.class );
-
-    public static boolean testUserPermissions(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final UserIdentity userIdentity,
-            final List<UserPermission> userPermissions
-    )
-            throws PwmUnrecoverableException
-    {
-        if ( userPermissions == null )
-        {
-            return false;
-        }
-
-        for ( final UserPermission userPermission : userPermissions )
-        {
-            if ( testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission ) )
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean testUserPermission(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            final UserIdentity userIdentity,
-            final UserPermission userPermission
-    )
-            throws PwmUnrecoverableException
-    {
-        if ( userPermission == null || userIdentity == null )
-        {
-            return false;
-        }
-
-        boolean profileAppliesToUser = false;
-        if ( userPermission.getLdapProfileID() == null
-                || userPermission.getLdapProfileID().isEmpty()
-                || userPermission.getLdapProfileID().equals( PwmConstants.PROFILE_ID_ALL ) )
-        {
-            profileAppliesToUser = true;
-        }
-        else if ( userIdentity.getLdapProfileID().equals( userPermission.getLdapProfileID() ) )
-        {
-            profileAppliesToUser = true;
-        }
-        if ( !profileAppliesToUser )
-        {
-            return false;
-        }
-
-        switch ( userPermission.getType() )
-        {
-            case ldapQuery:
-            {
-                if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().trim().isEmpty() )
-                {
-                    if ( !testUserDNmatch( pwmApplication, sessionLabel, userPermission.getLdapBase(), userIdentity ) )
-                    {
-                        return false;
-                    }
-                }
-
-                return testQueryMatch( pwmApplication, sessionLabel, userIdentity, userPermission.getLdapQuery() );
-            }
-
-            case ldapGroup:
-            {
-                return testGroupMatch( pwmApplication, sessionLabel, userIdentity, userPermission.getLdapBase() );
-            }
-
-            default:
-                throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_INTERNAL, "unknown permission type: " + userPermission.getType() ) );
-        }
-    }
-
-    public static boolean testGroupMatch(
-            final PwmApplication pwmApplication,
-            final SessionLabel pwmSession,
-            final UserIdentity userIdentity,
-            final String groupDN
-    )
-            throws PwmUnrecoverableException
-    {
-        final Instant startTime = Instant.now();
-
-        if ( userIdentity == null )
-        {
-            return false;
-        }
-
-        LOGGER.trace( pwmSession, () -> "begin check for ldapGroup match for " + userIdentity + " using queryMatch: " + groupDN );
-
-        boolean result = false;
-        if ( groupDN == null || groupDN.length() < 1 )
-        {
-            LOGGER.trace( pwmSession, () -> "missing groupDN value, skipping check" );
-        }
-        else
-        {
-            final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
-            final String filterString = "(" + ldapProfile.readSettingAsString( PwmSetting.LDAP_USER_GROUP_ATTRIBUTE ) + "=" + groupDN + ")";
-            try
-            {
-                LOGGER.trace( pwmSession, () -> "checking ldap to see if " + userIdentity + " matches group '" + groupDN + "' using filter '" + filterString + "'" );
-                final ChaiUser theUser = pwmApplication.getProxiedChaiUser( userIdentity );
-                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(
-                        theUser.getEntryDN(),
-                        filterString,
-                        Collections.<String>emptySet(), SearchScope.BASE
-                );
-                if ( results.size() == 1 && results.keySet().contains( theUser.getEntryDN() ) )
-                {
-                    result = true;
-                }
-            }
-            catch ( final ChaiException e )
-            {
-                LOGGER.warn( pwmSession, () -> "LDAP error during group for " + userIdentity + " using " + filterString + ", error:" + e.getMessage() );
-            }
-        }
-
-        {
-            final boolean finalResult = result;
-            LOGGER.debug( pwmSession, () -> "user " + userIdentity.toDisplayString() + " is "
-                    + ( finalResult ? "" : "not " )
-                    + "a match for group '" + groupDN + "'"
-                    + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
-        }
-
-        return result;
-    }
-
-    public static boolean testQueryMatch(
-            final PwmApplication pwmApplication,
-            final SessionLabel pwmSession,
-            final UserIdentity userIdentity,
-            final String filterString
-    )
-            throws PwmUnrecoverableException
-    {
-        final Instant startTime = Instant.now();
-
-        if ( userIdentity == null )
-        {
-            return false;
-        }
-
-        LOGGER.trace( pwmSession, () -> "begin check for ldapQuery match for " + userIdentity + " using queryMatch: " + filterString );
-
-        boolean result = false;
-        if ( filterString == null || filterString.length() < 1 )
-        {
-            LOGGER.trace( pwmSession, () -> "missing queryMatch value, skipping check" );
-        }
-        else if ( "(objectClass=*)".equalsIgnoreCase( filterString ) || "objectClass=*".equalsIgnoreCase( filterString ) )
-        {
-            LOGGER.trace( pwmSession, () -> "queryMatch check is guaranteed to be true, skipping ldap query" );
-            result = true;
-        }
-        else
-        {
-            try
-            {
-                LOGGER.trace( pwmSession, () -> "checking ldap to see if " + userIdentity + " matches '" + filterString + "'" );
-                final ChaiUser theUser = pwmApplication.getProxiedChaiUser( userIdentity );
-                final Map<String, Map<String, String>> results = theUser.getChaiProvider().search( theUser.getEntryDN(), filterString, Collections.emptySet(), SearchScope.BASE );
-                if ( results.size() == 1 && results.keySet().contains( theUser.getEntryDN() ) )
-                {
-                    result = true;
-                }
-            }
-            catch ( final ChaiException e )
-            {
-                LOGGER.warn( pwmSession, () -> "LDAP error during check for " + userIdentity + " using " + filterString + ", error:" + e.getMessage() );
-            }
-        }
-
-        {
-            final boolean finalResult = result;
-            LOGGER.debug( pwmSession, () -> "user " + userIdentity.toDisplayString() + " is "
-                    + ( finalResult ? "" : "not " )
-                    + "a match for filter '" + filterString + "'"
-                    + " (" + TimeDuration.fromCurrent( startTime ).asCompactString() + ")" );
-        }
-
-        return result;
-    }
-
-    public static Map<UserIdentity, Map<String, String>> discoverMatchingUsers(
-            final PwmApplication pwmApplication,
-            final int maxResultSize,
-            final List<UserPermission> userPermissions,
-            final SessionLabel sessionLabel
-    )
-            throws Exception
-    {
-        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
-
-        final Map<UserIdentity, Map<String, String>> results = new TreeMap<>();
-        for ( final UserPermission userPermission : userPermissions )
-        {
-            if ( ( maxResultSize ) - results.size() > 0 )
-            {
-                final SearchConfiguration searchConfiguration = SearchConfiguration.fromPermission( userPermission );
-
-                try
-                {
-                    results.putAll( userSearchEngine.performMultiUserSearch(
-                            searchConfiguration,
-                            ( maxResultSize ) - results.size(),
-                            Collections.emptyList(),
-                            sessionLabel
-                    ) );
-                }
-                catch ( final PwmUnrecoverableException e )
-                {
-                    LOGGER.error( () -> "error reading matching users: " + e.getMessage() );
-                    throw new PwmOperationalException( e.getErrorInformation() );
-                }
-            }
-        }
-
-        return results;
-    }
-
-    private static boolean testUserDNmatch(
-            final PwmApplication pwmApplication,
-            final SessionLabel sessionLabel,
-            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 );
-    }
-}

+ 58 - 0
server/src/main/java/password/pwm/ldap/permission/AllPermissionTypeHelper.java

@@ -0,0 +1,58 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.search.SearchConfiguration;
+
+class AllPermissionTypeHelper implements PermissionTypeHelper
+{
+    @Override
+    public boolean testMatch(
+            final PwmApplication pwmApplication,
+            final SessionLabel pwmSession,
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+            throws PwmUnrecoverableException
+    {
+        return true;
+    }
+
+    @Override
+    public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+        return SearchConfiguration.builder()
+                .username( "*" )
+                .enableValueEscaping( false )
+                .build();
+    }
+
+    @Override
+    public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+
+    }
+}

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

@@ -0,0 +1,106 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.PwmSetting;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+
+class LdapGroupTypeHelper implements PermissionTypeHelper
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( LdapGroupTypeHelper.class );
+
+    @Override
+    public boolean testMatch(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+            throws PwmUnrecoverableException
+    {
+        final Instant startTime = Instant.now();
+        final String groupDN = userPermission.getLdapQuery();
+
+        if ( userIdentity == null )
+        {
+            return false;
+        }
+
+        LOGGER.trace( sessionLabel, () -> "begin check for ldapGroup match for " + userIdentity + " using queryMatch: " + groupDN );
+
+        boolean result = false;
+        if ( StringUtil.isEmpty( groupDN ) )
+        {
+            LOGGER.trace( sessionLabel, () -> "missing groupDN value, skipping check" );
+        }
+        else
+        {
+            final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+            final String filterString = "(" + ldapProfile.readSettingAsString( PwmSetting.LDAP_USER_GROUP_ATTRIBUTE ) + "=" + groupDN + ")";
+            LOGGER.trace( sessionLabel, () -> "checking ldap to see if " + userIdentity + " matches group '" + groupDN + "' using filter '" + filterString + "'" );
+            result = LdapQueryHelper.selfUserSearch( pwmApplication, sessionLabel, userIdentity, filterString );
+
+        }
+
+        {
+            final boolean finalResult = result;
+            LOGGER.debug( sessionLabel, () -> "user " + userIdentity.toDisplayString() + " is "
+                    + ( finalResult ? "" : "not " )
+                    + "a match for group '" + groupDN + "'"
+                    + " (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
+        }
+
+        return result;
+    }
+
+    @Override
+    public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission )
+            throws PwmUnrecoverableException
+    {
+        return SearchConfiguration.builder()
+                .groupDN( userPermission.getLdapBase() )
+                .ldapProfile( UserPermissionTester.profileIdForPermission( userPermission ) )
+                .build();
+    }
+
+    @Override
+    public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+        if ( StringUtil.isEmpty( userPermission.getLdapBase() ) )
+        {
+            throw PwmUnrecoverableException.newException(
+                    PwmError.CONFIG_FORMAT_ERROR,
+                    "userPermission of type " + UserPermissionType.ldapGroup + " must have a ldapBase value" );
+        }
+    }
+}

+ 155 - 0
server/src/main/java/password/pwm/ldap/permission/LdapQueryHelper.java

@@ -0,0 +1,155 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+import com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiException;
+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;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.Collections;
+import java.util.Map;
+
+class LdapQueryHelper implements PermissionTypeHelper
+{
+
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionTester.class );
+
+    public boolean testMatch(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().trim().isEmpty() )
+        {
+            if ( !testBaseDnMatch( pwmApplication, userPermission.getLdapBase(), userIdentity ) )
+            {
+                return false;
+            }
+        }
+
+        if ( userIdentity == null )
+        {
+            return false;
+        }
+
+        final String filterString = userPermission.getLdapQuery();
+        LOGGER.trace( sessionLabel, () -> "begin check for ldapQuery match for " + userIdentity + " using queryMatch: " + filterString );
+
+        if ( StringUtil.isEmpty( filterString ) )
+        {
+            LOGGER.trace( sessionLabel, () -> "missing queryMatch value, skipping check" );
+            return false;
+        }
+
+        if ( "(objectClass=*)".equalsIgnoreCase( filterString ) || "objectClass=*".equalsIgnoreCase( filterString ) )
+        {
+            LOGGER.trace( sessionLabel, () -> "queryMatch check is guaranteed to be true, skipping ldap query" );
+            return true;
+        }
+
+        LOGGER.trace( sessionLabel, () -> "checking ldap to see if " + userIdentity + " matches '" + filterString + "'" );
+        return selfUserSearch( pwmApplication, sessionLabel, userIdentity, filterString );
+    }
+
+    static boolean selfUserSearch(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final String searchFilter
+    )
+            throws PwmUnrecoverableException
+    {
+        try
+        {
+            final ChaiUser theUser = pwmApplication.getProxiedChaiUser( userIdentity );
+            final Map<String, Map<String, String>> results = theUser.getChaiProvider().search(
+                    theUser.getEntryDN(),
+                    searchFilter,
+                    Collections.emptySet(),
+                    SearchScope.BASE );
+
+            if ( results.size() == 1 && results.containsKey( theUser.getEntryDN() ) )
+            {
+                return true;
+            }
+        }
+        catch ( final ChaiException e )
+        {
+            LOGGER.warn( sessionLabel, () -> "LDAP error during check for " + userIdentity + " using " + searchFilter + ", error:" + e.getMessage() );
+        }
+
+        return false;
+    }
+
+
+    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 ) )
+                .build();
+    }
+
+    @Override
+    public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+        if ( StringUtil.isEmpty( userPermission.getLdapQuery() ) )
+        {
+            throw PwmUnrecoverableException.newException(
+                    PwmError.CONFIG_FORMAT_ERROR,
+                    "userPermission of type " + UserPermissionType.ldapQuery + " must have a ldapQuery value" );
+        }
+
+        StringUtil.validateLdapSearchFilter( userPermission.getLdapQuery() );
+    }
+}

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

@@ -0,0 +1,91 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+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;
+import password.pwm.ldap.search.SearchConfiguration;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.util.Collections;
+import java.util.Objects;
+
+class LdapUserDNTypeHelper implements PermissionTypeHelper
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( LdapUserDNTypeHelper.class );
+
+    @Override
+    public boolean testMatch(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+            throws PwmUnrecoverableException
+    {
+        final String groupDN = userPermission.getLdapQuery();
+
+        if ( userIdentity == null )
+        {
+            return false;
+        }
+
+        LOGGER.trace( sessionLabel, () -> "begin check for userDN match for " + userIdentity + " using compare DN" );
+        if ( StringUtil.isEmpty( groupDN ) )
+        {
+            LOGGER.trace( sessionLabel, () -> "missing userDN value, skipping check" );
+        }
+
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile( pwmApplication.getConfig() );
+        final String userCanonicalDN = ldapProfile.readCanonicalDN( pwmApplication, userIdentity.getUserDN() );
+        final String configuredCanonicalDN = ldapProfile.readCanonicalDN( pwmApplication, userPermission.getLdapBase() );
+        return Objects.equals( userCanonicalDN, configuredCanonicalDN );
+    }
+
+    @Override
+    public SearchConfiguration searchConfigurationFromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+        return SearchConfiguration.builder()
+                .filter( "(objectClass=*)" )
+                .enableContextValidation( false )
+                .ldapProfile( UserPermissionTester.profileIdForPermission( userPermission ) )
+                .contexts( Collections.singletonList( userPermission.getLdapBase() ) )
+                .searchScope( SearchConfiguration.SearchScope.base )
+                .build();
+    }
+
+    @Override
+    public void validatePermission( final UserPermission userPermission ) throws PwmUnrecoverableException
+    {
+        if ( StringUtil.isEmpty( userPermission.getLdapBase() ) )
+        {
+            throw PwmUnrecoverableException.newException(
+                    PwmError.CONFIG_FORMAT_ERROR,
+                    "userPermission of type " + UserPermissionType.ldapUser + " must have a ldapBase value" );
+        }
+    }
+}

+ 48 - 0
server/src/main/java/password/pwm/ldap/permission/PermissionTypeHelper.java

@@ -0,0 +1,48 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+import password.pwm.PwmApplication;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.search.SearchConfiguration;
+
+import java.io.Serializable;
+
+interface PermissionTypeHelper extends Serializable
+{
+    boolean testMatch(
+            PwmApplication pwmApplication,
+            SessionLabel pwmSession,
+            UserIdentity userIdentity,
+            UserPermission userPermission
+    )
+            throws PwmUnrecoverableException;
+
+    SearchConfiguration searchConfigurationFromPermission( UserPermission userPermission )
+            throws PwmUnrecoverableException;
+
+    void validatePermission( UserPermission userPermission )
+        throws PwmUnrecoverableException;
+
+}

+ 205 - 0
server/src/main/java/password/pwm/ldap/permission/UserPermissionTester.java

@@ -0,0 +1,205 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.SessionLabel;
+import password.pwm.bean.UserIdentity;
+import password.pwm.config.value.data.UserPermission;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmOperationalException;
+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.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class UserPermissionTester
+{
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserPermissionTester.class );
+
+    public static boolean testUserPermission(
+            final CommonValues commonValues,
+            final UserIdentity userIdentity,
+            final UserPermission userPermissions
+    )
+            throws PwmUnrecoverableException
+    {
+        return testUserPermission(
+                commonValues.getPwmApplication(),
+                commonValues.getSessionLabel(),
+                userIdentity,
+                Collections.singletonList( userPermissions ) );
+    }
+
+    public static boolean testUserPermission(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final List<UserPermission> userPermissions
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( userPermissions == null )
+        {
+            return false;
+        }
+
+        final List<UserPermission> sortedList = new ArrayList<>( userPermissions );
+        Collections.sort( sortedList );
+
+        for ( final UserPermission userPermission : sortedList )
+        {
+            if ( testUserPermission( pwmApplication, sessionLabel, userIdentity, userPermission ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean checkIfProfileAppliesToUser(
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+    {
+        return userPermission.getLdapProfileID() == null
+                || userPermission.getLdapProfileID().isEmpty()
+                || userPermission.getLdapProfileID().equals( PwmConstants.PROFILE_ID_ALL )
+                || userIdentity.getLdapProfileID().equals( userPermission.getLdapProfileID() );
+    }
+
+    private static boolean testUserPermission(
+            final PwmApplication pwmApplication,
+            final SessionLabel sessionLabel,
+            final UserIdentity userIdentity,
+            final UserPermission userPermission
+    )
+            throws PwmUnrecoverableException
+    {
+        if ( userPermission == null || userIdentity == null )
+        {
+            return false;
+        }
+
+        if ( !checkIfProfileAppliesToUser( userIdentity, userPermission ) )
+        {
+            return false;
+        }
+
+        final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester();
+        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 + "'",
+                () -> TimeDuration.fromCurrent( startTime ) );
+        return match;
+    }
+
+    public static List<UserIdentity> discoverMatchingUsers(
+            final PwmApplication pwmApplication,
+            final List<UserPermission> userPermissions,
+            final SessionLabel sessionLabel,
+            final int maxResultSize,
+            final TimeDuration maxSearchTime
+    )
+            throws PwmUnrecoverableException, PwmOperationalException
+    {
+        if ( userPermissions == null )
+        {
+            return Collections.emptyList();
+        }
+
+        final List<UserPermission> sortedPermissions = new ArrayList<>( userPermissions );
+        Collections.sort( sortedPermissions );
+
+        final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+        final List<UserIdentity> resultSet = new ArrayList<>();
+
+        for ( final UserPermission userPermission : sortedPermissions )
+        {
+            if ( ( maxResultSize ) - resultSet.size() > 0 )
+            {
+                final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester();
+                final SearchConfiguration searchConfiguration = permissionTypeHelper.searchConfigurationFromPermission( userPermission )
+                        .toBuilder()
+                        .searchTimeout( maxSearchTime )
+                        .build();
+
+                try
+                {
+                    final Map<UserIdentity, Map<String, String>> results = userSearchEngine.performMultiUserSearch(
+                            searchConfiguration,
+                            ( maxResultSize ) - resultSet.size(),
+                            Collections.emptyList(),
+                            sessionLabel
+                    );
+
+                    resultSet.addAll( results.keySet() );
+                }
+                catch ( final PwmUnrecoverableException e )
+                {
+                    LOGGER.error( () -> "error reading matching users: " + e.getMessage() );
+                    throw new PwmOperationalException( e.getErrorInformation() );
+                }
+            }
+        }
+
+        return Collections.unmodifiableList( resultSet );
+    }
+
+    static String profileIdForPermission( final UserPermission userPermission )
+    {
+        if ( userPermission.getLdapProfileID() != null
+                && !userPermission.getLdapProfileID().isEmpty()
+                && !userPermission.getLdapProfileID().equals( PwmConstants.PROFILE_ID_ALL ) )
+        {
+            return userPermission.getLdapProfileID();
+        }
+
+        return null;
+    }
+
+
+    public static void validatePermissionSyntax( final UserPermission userPermission )
+            throws PwmUnrecoverableException
+    {
+        Objects.requireNonNull( userPermission );
+
+        if ( userPermission.getType() == null )
+        {
+            throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, "userPermission must have a type value" );
+        }
+
+        final PermissionTypeHelper permissionTypeHelper = userPermission.getType().getPermissionTypeTester();
+        permissionTypeHelper.validatePermission( userPermission );
+    }
+}

+ 56 - 0
server/src/main/java/password/pwm/ldap/permission/UserPermissionType.java

@@ -0,0 +1,56 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.ldap.permission;
+
+public enum UserPermissionType
+{
+    ldapAllUsers( new AllPermissionTypeHelper(), "All Users", "" ),
+    ldapUser( new LdapUserDNTypeHelper(), "LDAP User", "User" ),
+    ldapGroup( new LdapGroupTypeHelper(), "LDAP Group", "Group" ),
+    ldapQuery( new LdapQueryHelper(), "LDAP Query", "Base" ),;
+
+    private final PermissionTypeHelper permissionTypeHelper;
+    private final String label;
+    private final String baseLabel;
+
+    UserPermissionType( final PermissionTypeHelper permissionTypeHelper, final String label, final String baseLabel )
+    {
+
+        this.permissionTypeHelper = permissionTypeHelper;
+        this.label = label;
+        this.baseLabel = baseLabel;
+    }
+
+    public PermissionTypeHelper getPermissionTypeTester()
+    {
+        return permissionTypeHelper;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public String getBaseLabel()
+    {
+        return baseLabel;
+    }
+}

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

@@ -23,15 +23,10 @@ package password.pwm.ldap.search;
 import com.novell.ldapchai.provider.ChaiProvider;
 import lombok.Builder;
 import lombok.Value;
-import password.pwm.PwmConstants;
 import password.pwm.config.value.data.FormConfiguration;
-import password.pwm.config.value.data.UserPermission;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.util.java.TimeDuration;
 
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -48,7 +43,7 @@ public class SearchConfiguration implements Serializable
     private List<String> contexts;
     private Map<FormConfiguration, String> formValues;
     private transient ChaiProvider chaiProvider;
-    private long searchTimeout;
+    private TimeDuration searchTimeout;
 
     @Builder.Default
     private boolean ignoreOperationalErrors = false;
@@ -90,44 +85,4 @@ public class SearchConfiguration implements Serializable
             throw new IllegalArgumentException( "username OR formRows cannot both be supplied" );
         }
     }
-
-    public static SearchConfiguration fromPermission( final UserPermission userPermission ) throws PwmUnrecoverableException
-    {
-        final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
-
-        switch ( userPermission.getType() )
-        {
-            case ldapQuery:
-            {
-                builder.filter( userPermission.getLdapQuery() );
-                if ( userPermission.getLdapBase() != null && !userPermission.getLdapBase().isEmpty() )
-                {
-                    builder.enableContextValidation( false );
-                    builder.contexts( Collections.singletonList( userPermission.getLdapBase() ) );
-                }
-            }
-            break;
-
-            case ldapGroup:
-            {
-                builder.groupDN( userPermission.getLdapBase() );
-            }
-            break;
-
-            default:
-                throw new PwmUnrecoverableException( new ErrorInformation(
-                        PwmError.ERROR_INTERNAL,
-                        "unknown permission type: " + userPermission.getType() )
-                );
-        }
-
-        if ( userPermission.getLdapProfileID() != null
-                && !userPermission.getLdapProfileID().isEmpty()
-                && !userPermission.getLdapProfileID().equals( PwmConstants.PROFILE_ID_ALL ) )
-        {
-            builder.ldapProfile( userPermission.getLdapProfileID() );
-        }
-
-        return builder.build();
-    }
 }

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

@@ -137,7 +137,7 @@ public class UserSearchEngine implements PwmService
     @Override
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.emptyList(), debugProperties() );
+        return ServiceInfoBean.builder().debugProperties( debugProperties() ).build();
     }
 
     public UserIdentity resolveUsername(
@@ -432,8 +432,8 @@ public class UserSearchEngine implements PwmService
             searchContexts = ldapProfile.getRootContexts( pwmApplication );
         }
 
-        final long timeLimitMS = searchConfiguration.getSearchTimeout() > 0
-                ? searchConfiguration.getSearchTimeout()
+        final long timeLimitMS = searchConfiguration.getSearchTimeout() != null
+                ? searchConfiguration.getSearchTimeout().asMillis()
                 : ( ldapProfile.readSettingAsLong( PwmSetting.LDAP_SEARCH_TIMEOUT ) * 1000 );
 
         final ChaiProvider chaiProvider = searchConfiguration.getChaiProvider() == null

+ 11 - 13
server/src/main/java/password/pwm/svc/PwmService.java

@@ -20,8 +20,9 @@
 
 package password.pwm.svc;
 
-import lombok.AllArgsConstructor;
-import lombok.Getter;
+import lombok.Builder;
+import lombok.Singular;
+import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmException;
@@ -29,9 +30,9 @@ import password.pwm.health.HealthRecord;
 
 import java.io.Serializable;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * An interface for daemon/background services.  Services are initialized, shutdown and accessed via {@link PwmApplication}.  Some services
@@ -57,22 +58,19 @@ public interface PwmService
 
     interface ServiceInfo
     {
-        Collection<DataStorageMethod> getUsedStorageMethods( );
+        Collection<DataStorageMethod> getStorageMethods( );
 
         Map<String, String> getDebugProperties( );
     }
 
-    @Getter
-    @AllArgsConstructor
+    @Value
+    @Builder
     class ServiceInfoBean implements ServiceInfo, Serializable
     {
-        private final Collection<DataStorageMethod> usedStorageMethods;
-        private final Map<String, String> debugProperties;
+        @Singular
+        private final Set<DataStorageMethod> storageMethods;
 
-        public ServiceInfoBean( final Collection<DataStorageMethod> usedStorageMethods )
-        {
-            this.usedStorageMethods = usedStorageMethods;
-            this.debugProperties = Collections.emptyMap();
-        }
+        @Singular
+        private final Map<String, String> debugProperties;
     }
 }

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

@@ -111,7 +111,7 @@ public class CacheService implements PwmService
     {
         if ( status == STATUS.CLOSED )
         {
-            return new ServiceInfoBean( Collections.emptyList(), Collections.emptyMap() );
+            return ServiceInfoBean.builder().build();
         }
 
         final Map<String, String> debugInfo = new TreeMap<>( );
@@ -119,7 +119,7 @@ public class CacheService implements PwmService
         debugInfo.put( "byteCount", String.valueOf( memoryCacheStore.byteCount() ) );
         debugInfo.putAll( JsonUtil.deserializeStringMap( JsonUtil.serialize( memoryCacheStore.getCacheStoreInfo() ) ) );
         debugInfo.putAll( JsonUtil.deserializeStringMap( JsonUtil.serializeMap( memoryCacheStore.storedClassHistogram( "histogram." ) ) ) );
-        return new ServiceInfoBean( Collections.emptyList(), debugInfo );
+        return ServiceInfoBean.builder().debugProperties( debugInfo ).build();
     }
 
     public Map<String, Serializable> debugInfo( )

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

@@ -109,7 +109,7 @@ public class EmailService implements PwmService
 
         if ( pwmApplication.getLocalDB() == null || pwmApplication.getLocalDB().status() != LocalDB.Status.OPEN )
         {
-            LOGGER.warn( () -> "localDB is not open, EmailService will remain closed" );
+            LOGGER.debug( () -> "localDB is not open, EmailService will remain closed" );
             status = STATUS.CLOSED;
             return;
         }
@@ -189,16 +189,17 @@ public class EmailService implements PwmService
     @Override
     public ServiceInfoBean serviceInfo( )
     {
-        final Map<String, String> debugItems = stats();
-
         if ( status() == STATUS.OPEN )
         {
-            return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ), debugItems );
-        }
-        else
-        {
-            return new ServiceInfoBean( Collections.emptyList(), debugItems );
+            return ServiceInfoBean.builder()
+                    .storageMethod( DataStorageMethod.LOCALDB )
+                    .debugProperties( stats() )
+                    .build();
         }
+
+        return ServiceInfoBean.builder()
+                .debugProperties( stats() )
+                .build();
     }
 
     public int queueSize( )

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

@@ -84,10 +84,10 @@ public enum AuditEvent
     private final Message message;
     private final PwmDisplayBundle narrative;
 
-    private String xdasTaxonomy;
-    private String xdasOutcome;
+    private final String xdasTaxonomy;
+    private final String xdasOutcome;
 
-    private Type type;
+    private final Type type;
 
     AuditEvent( final Message message, final PwmDisplayBundle narrative, final Type type )
     {

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

@@ -70,7 +70,7 @@ public class AuditService implements PwmService
 
     private STATUS status = STATUS.CLOSED;
     private AuditSettings settings;
-    private ServiceInfoBean serviceInfo = new ServiceInfoBean( Collections.emptyList() );
+    private ServiceInfoBean serviceInfo = ServiceInfoBean.builder().build();
 
     private SyslogAuditService syslogManager;
     private ErrorInformation lastError;
@@ -165,7 +165,7 @@ public class AuditService implements PwmService
                     return;
             }
             LOGGER.debug( () -> debugMsg, () -> TimeDuration.fromCurrent( startTime ) );
-            serviceInfo = new ServiceInfoBean( Collections.singletonList( storageMethodUsed ) );
+            serviceInfo = ServiceInfoBean.builder().storageMethod( storageMethodUsed ).build();
         }
         {
             final TimeDuration maxRecordAge = TimeDuration.of( pwmApplication.getConfig().readSettingAsLong( PwmSetting.EVENTS_AUDIT_MAX_AGE ), TimeDuration.Unit.SECONDS );

+ 4 - 20
server/src/main/java/password/pwm/svc/event/AuditVault.java

@@ -20,6 +20,7 @@
 
 package password.pwm.svc.event;
 
+import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
@@ -47,27 +48,10 @@ public interface AuditVault
 
     void add( AuditRecord record ) throws PwmOperationalException;
 
+    @Value
     class Settings
     {
-        private long maxRecordCount;
-        private TimeDuration maxRecordAge;
-
-
-        public Settings( final long maxRecordCount, final TimeDuration maxRecordAge )
-        {
-            this.maxRecordCount = maxRecordCount;
-            this.maxRecordAge = maxRecordAge;
-        }
-
-        public long getMaxRecordCount( )
-        {
-            return maxRecordCount;
-        }
-
-        public TimeDuration getMaxRecordAge( )
-        {
-            return maxRecordAge;
-        }
+        private final long maxRecordCount;
+        private final TimeDuration maxRecordAge;
     }
-
 }

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

@@ -25,6 +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.logging.PwmLogger;
 
 import java.util.Collections;
@@ -35,7 +36,6 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class HttpClientService implements PwmService
 {
@@ -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 Map<StatsKey, AtomicInteger> stats = new HashMap<>( );
+    private final StatisticIntCounterMap<StatsKey> stats = new StatisticIntCounterMap<>( StatsKey.class );
 
     enum StatsKey
     {
@@ -57,10 +57,6 @@ public class HttpClientService implements PwmService
     public HttpClientService()
             throws PwmUnrecoverableException
     {
-        for ( final StatsKey statsKey : StatsKey.values() )
-        {
-            stats.put( statsKey, new AtomicInteger( 0 ) );
-        }
     }
 
     @Override
@@ -110,14 +106,14 @@ public class HttpClientService implements PwmService
         final PwmHttpClient existingClient = threadLocal.get();
         if ( existingClient != null && !existingClient.isClosed() )
         {
-            stats.get( StatsKey.reusedClients ).incrementAndGet();
+            stats.increment( StatsKey.reusedClients );
             return existingClient;
         }
 
         final PwmHttpClient newClient = new PwmHttpClient( pwmApplication, pwmHttpClientConfiguration );
         issuedClients.put( newClient, null );
         threadLocal.set( newClient );
-        stats.get( StatsKey.createdClients ).incrementAndGet();
+        stats.increment( StatsKey.createdClients );
         return newClient;
     }
 
@@ -130,10 +126,12 @@ public class HttpClientService implements PwmService
     @Override
     public ServiceInfoBean serviceInfo()
     {
-        final Map<String, String> debugMap = new HashMap<>(  );
-        stats.forEach( ( key, value ) -> debugMap.put( key.name(), value.toString() ) );
+        final Map<String, String> debugMap = new HashMap<>( stats.debugStats() );
         debugMap.put( "weakReferences", Integer.toString( issuedClients.size() ) );
         debugMap.put( "referencedConfigs", Integer.toString( clients.size() ) );
-        return new ServiceInfoBean( Collections.emptyList(), debugMap );
+        return ServiceInfoBean.builder()
+                .debugProperties( debugMap )
+                .build();
+
     }
 }

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

@@ -21,25 +21,25 @@
 package password.pwm.svc.httpclient;
 
 import lombok.Builder;
+import lombok.Singular;
 import lombok.Value;
 import password.pwm.http.HttpEntityDataType;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.bean.ImmutableByteArray;
+import password.pwm.util.java.AtomicLoopIntIncrementer;
 import password.pwm.util.java.StringUtil;
 
 import java.io.Serializable;
 import java.net.URI;
-import java.util.Collections;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
 
 @Value
 @Builder( toBuilder = true )
 public class PwmHttpClientRequest implements Serializable, PwmHttpClientMessage
 {
-    private static final AtomicInteger REQUEST_COUNTER = new AtomicInteger( 0 );
+    private static final AtomicLoopIntIncrementer REQUEST_COUNTER = new AtomicLoopIntIncrementer();
 
-    private final int requestID = REQUEST_COUNTER.incrementAndGet();
+    private final int requestID = REQUEST_COUNTER.next();
 
     @Builder.Default
     private final HttpMethod method = HttpMethod.GET;
@@ -52,8 +52,8 @@ public class PwmHttpClientRequest implements Serializable, PwmHttpClientMessage
 
     private final ImmutableByteArray binaryBody = null;
 
-    @Builder.Default
-    private final Map<String, String> headers = Collections.emptyMap();
+    @Singular
+    private final Map<String, String> headers;
 
     public String toDebugString( final PwmHttpClient pwmHttpClient, final String additionalText )
     {

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

@@ -87,7 +87,7 @@ public class IntruderManager implements PwmService
 
     private final Map<RecordType, RecordManager> recordManagers = new HashMap<>();
 
-    private ServiceInfoBean serviceInfo = new ServiceInfoBean( Collections.emptyList() );
+    private ServiceInfoBean serviceInfo = ServiceInfoBean.builder().build();
 
     public IntruderManager( )
     {
@@ -163,7 +163,7 @@ public class IntruderManager implements PwmService
                     return;
             }
             LOGGER.info( () -> debugMsg );
-            serviceInfo = new ServiceInfoBean( Collections.singletonList( storageMethodUsed ) );
+            serviceInfo = ServiceInfoBean.builder().storageMethod( storageMethodUsed ).build();
         }
         final RecordStore recordStore;
         {

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

@@ -22,12 +22,12 @@ package password.pwm.svc.node;
 
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
-import lombok.Getter;
+import lombok.Value;
 
 import java.io.Serializable;
 import java.time.Instant;
 
-@Getter
+@Value
 @AllArgsConstructor( access = AccessLevel.PACKAGE )
 public class NodeInfo implements Serializable
 {

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

@@ -163,7 +163,10 @@ public class NodeService implements PwmService
         {
             props.putAll( JsonUtil.deserializeStringMap( JsonUtil.serialize( nodeMachine.getNodeServiceStatistics() ) ) );
         }
-        return new ServiceInfoBean( Collections.singleton( dataStore ), props );
+        return ServiceInfoBean.builder()
+                .storageMethod( dataStore )
+                .debugProperties( props )
+                .build();
     }
 
     public boolean isMaster( )

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

@@ -33,6 +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.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.PwmScheduler;
@@ -48,10 +49,11 @@ import java.io.Writer;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
+import java.util.ArrayDeque;
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
+import java.util.Queue;
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
@@ -155,17 +157,16 @@ public class PwNotifyEngine
             }
 
             log( "starting job, beginning ldap search" );
-            final Iterator<UserIdentity> workQueue = LdapOperationsHelper.readUsersFromLdapForPermissions(
+            final Queue<UserIdentity> workQueue = new ArrayDeque<>( UserPermissionTester.discoverMatchingUsers(
                     pwmApplication,
-                    SESSION_LABEL,
-                    permissionList,
-                    settings.getMaxLdapSearchSize()
-            );
+                    permissionList, SESSION_LABEL, settings.getMaxLdapSearchSize(),
+                    settings.getSearchTimeout()
+            ) );
 
             log( "ldap search complete, examining users..." );
 
             final ThreadPoolExecutor threadPoolExecutor = createExecutor( pwmApplication );
-            while ( workQueue.hasNext() )
+            while ( workQueue.peek() != null )
             {
                 if ( !checkIfRunningOnMaster() || cancelFlag.get() )
                 {
@@ -174,7 +175,7 @@ public class PwNotifyEngine
                     throw PwmUnrecoverableException.newException( PwmError.ERROR_SERVICE_NOT_AVAILABLE, msg );
                 }
 
-                threadPoolExecutor.submit( new ProcessJob( workQueue.next() ) );
+                threadPoolExecutor.submit( new ProcessJob( workQueue.poll() ) );
             }
 
             JavaHelper.closeAndWaitExecutor( threadPoolExecutor, TimeDuration.DAY );

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

@@ -243,7 +243,7 @@ public class PwNotifyService extends AbstractPwmService implements PwmService
     @Override
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.singleton( storageMethod ), Collections.emptyMap() );
+        return ServiceInfoBean.builder().storageMethod( storageMethod ).build();
     }
 
     public void executeJob( )

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

@@ -41,6 +41,7 @@ class PwNotifySettings implements Serializable
     private final TimeDuration maximumSkipWindow;
     private final TimeDuration zuluOffset;
     private final int maxLdapSearchSize;
+    private final TimeDuration searchTimeout;
     private final int batchCount;
     private final BigDecimal batchTimeMultiplier;
 
@@ -58,6 +59,7 @@ class PwNotifySettings implements Serializable
             builder.notificationIntervals( Collections.unmodifiableList( timeDurations ) );
         }
 
+        builder.searchTimeout( TimeDuration.of( Long.parseLong( configuration.readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ), TimeDuration.Unit.MILLISECONDS ) );
         builder.zuluOffset( TimeDuration.of( configuration.readSettingAsLong( PwmSetting.PW_EXPY_NOTIFY_JOB_OFFSET ), TimeDuration.Unit.SECONDS ) );
         builder.batchCount( Integer.parseInt( configuration.readAppProperty( AppProperty.PWNOTIFY_BATCH_COUNT ) ) );
         builder.maxLdapSearchSize( Integer.parseInt( configuration.readAppProperty( AppProperty.PWNOTIFY_MAX_LDAP_SEARCH_SIZE ) ) );

+ 24 - 9
server/src/main/java/password/pwm/svc/report/ReportService.java

@@ -33,9 +33,9 @@ import password.pwm.error.PwmException;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
-import password.pwm.ldap.LdapOperationsHelper;
 import password.pwm.ldap.UserInfo;
 import password.pwm.ldap.UserInfoFactory;
+import password.pwm.ldap.permission.UserPermissionTester;
 import password.pwm.svc.PwmService;
 import password.pwm.util.EventRateMeter;
 import password.pwm.util.PwmScheduler;
@@ -53,8 +53,8 @@ import password.pwm.util.logging.PwmLogger;
 
 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;
@@ -186,7 +186,7 @@ public class ReportService implements PwmService
     @Override
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LDAP ) );
+        return ServiceInfoBean.builder().storageMethod( DataStorageMethod.LDAP ).build();
     }
 
     public void executeCommand( final ReportCommand reportCommand )
@@ -377,16 +377,31 @@ public class ReportService implements PwmService
             resetJobStatus();
             clearWorkQueue();
 
-            final Iterator<UserIdentity> memQueue = LdapOperationsHelper.readUsersFromLdapForPermissions(
+            final Queue<UserIdentity> memQueue = new ArrayDeque<>( UserPermissionTester.discoverMatchingUsers(
                     pwmApplication,
-                    SessionLabel.REPORTING_SESSION_LABEL,
-                    settings.getSearchFilter(),
-                    settings.getMaxSearchSize()
-            );
+                    settings.getSearchFilter(), SessionLabel.REPORTING_SESSION_LABEL, settings.getMaxSearchSize(),
+                    settings.getSearchTimeout()
+            ) );
 
             LOGGER.trace( SessionLabel.REPORTING_SESSION_LABEL, () -> "completed ldap search process (" + TimeDuration.compactFromCurrent( startTime ) + ")" );
 
-            writeUsersToLocalDBQueue( memQueue );
+            final Iterator<UserIdentity> iterator = new Iterator<UserIdentity>()
+            {
+                @Override
+                public boolean hasNext( )
+                {
+                    return memQueue.peek() != null;
+                }
+
+                @Override
+                public UserIdentity next( )
+                {
+                    return memQueue.poll();
+                }
+            };
+
+
+            writeUsersToLocalDBQueue( iterator );
         }
 
         private void writeUsersToLocalDBQueue( final Iterator<UserIdentity> identityQueue )

+ 4 - 0
server/src/main/java/password/pwm/svc/report/ReportSettings.java

@@ -58,6 +58,9 @@ class ReportSettings implements Serializable
     @Builder.Default
     private int maxSearchSize = 100 * 1000;
 
+    @Builder.Default
+    private TimeDuration searchTimeout = TimeDuration.SECONDS_30;
+
     @Builder.Default
     private List<Integer> trackDays = Collections.emptyList();
 
@@ -81,6 +84,7 @@ class ReportSettings implements Serializable
         builder.searchFilter( config.readSettingAsUserPermission( PwmSetting.REPORTING_USER_MATCH ) );
         builder.maxSearchSize ( ( int ) config.readSettingAsLong( PwmSetting.REPORTING_MAX_QUERY_SIZE ) );
         builder.dailyJobEnabled( config.readSettingAsBoolean( PwmSetting.REPORTING_ENABLE_DAILY_JOB ) );
+        builder.searchTimeout( TimeDuration.of( Long.parseLong( config.readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ), TimeDuration.Unit.MILLISECONDS ) );
 
         {
             final List<UserPermission> userMatches = config.readSettingAsUserPermission( PwmSetting.REPORTING_USER_MATCH );

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

@@ -163,7 +163,7 @@ public class UserCacheService implements PwmService
 
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ) );
+        return ServiceInfoBean.builder().storageMethod( DataStorageMethod.LOCALDB ).build();
     }
 
     public long size( )

+ 1 - 2
server/src/main/java/password/pwm/svc/shorturl/UrlShortenerService.java

@@ -24,7 +24,6 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.option.DataStorageMethod;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
@@ -163,6 +162,6 @@ public class UrlShortenerService implements PwmService
 
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.<DataStorageMethod>emptyList() );
+        return ServiceInfoBean.builder().build();
     }
 }

+ 3 - 3
server/src/main/java/password/pwm/svc/stats/StatisticsManager.java

@@ -236,7 +236,7 @@ public class StatisticsManager implements PwmService
 
         if ( localDB == null )
         {
-            LOGGER.error( () -> "LocalDB is not available, will remain closed" );
+            LOGGER.debug( () -> "LocalDB is not available, will remain closed" );
             status = STATUS.CLOSED;
             return;
         }
@@ -447,8 +447,8 @@ public class StatisticsManager implements PwmService
     public ServiceInfoBean serviceInfo( )
     {
         return status() == STATUS.OPEN
-                ? new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ) )
-                : new ServiceInfoBean( Collections.emptyList() );
+                ? ServiceInfoBean.builder().storageMethod( DataStorageMethod.LOCALDB ).build()
+                : ServiceInfoBean.builder().build();
     }
 
     public static void incrementStat(

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

@@ -258,7 +258,7 @@ public class TelemetryService implements PwmService
         {
             debugMap.put( "lastError", lastError.toDebugStr() );
         }
-        return new ServiceInfoBean( null, Collections.unmodifiableMap( debugMap ) );
+        return ServiceInfoBean.builder().debugProperties( debugMap ).build();
     }
 
 

+ 0 - 1
server/src/main/java/password/pwm/svc/token/LdapTokenMachine.java

@@ -42,7 +42,6 @@ import java.util.Optional;
 
 class LdapTokenMachine implements TokenMachine
 {
-
     private static final String KEY_VALUE_DELIMITER = " ";
 
     private PwmApplication pwmApplication;

+ 17 - 3
server/src/main/java/password/pwm/svc/token/TokenService.java

@@ -25,6 +25,7 @@ import lombok.Builder;
 import lombok.Value;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
+import password.pwm.PwmApplicationMode;
 import password.pwm.PwmConstants;
 import password.pwm.bean.EmailItemBean;
 import password.pwm.bean.SessionLabel;
@@ -72,7 +73,6 @@ import password.pwm.util.secure.PwmRandom;
 
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -98,7 +98,7 @@ public class TokenService implements PwmService
     private TokenStorageMethod storageMethod;
     private TokenMachine tokenMachine;
 
-    private ServiceInfoBean serviceInfo = new ServiceInfoBean( Collections.emptyList() );
+    private ServiceInfoBean serviceInfo = ServiceInfoBean.builder().build();
     private volatile STATUS status = STATUS.CLOSED;
 
     private ErrorInformation errorInformation = null;
@@ -146,6 +146,18 @@ public class TokenService implements PwmService
             throw new PwmOperationalException( errorInformation );
         }
 
+        if ( pwmApplication.getLocalDB() == null )
+        {
+            LOGGER.trace( () -> "localDB is not available, will remain closed" );
+            return;
+        }
+
+        if ( pwmApplication.getApplicationMode() != PwmApplicationMode.RUNNING )
+        {
+            LOGGER.trace( () -> "Application mode is not 'running', will remain closed." );
+            return;
+        }
+
         try
         {
             DataStorageMethod usedStorageMethod = null;
@@ -180,7 +192,9 @@ public class TokenService implements PwmService
                 default:
                     JavaHelper.unhandledSwitchStatement( storageMethod );
             }
-            serviceInfo = new ServiceInfoBean( Collections.singletonList( usedStorageMethod ) );
+            serviceInfo = ServiceInfoBean.builder()
+                    .storageMethod( usedStorageMethod )
+                    .build();
         }
         catch ( final PwmException e )
         {

+ 7 - 6
server/src/main/java/password/pwm/svc/wordlist/AbstractWordlist.java

@@ -470,12 +470,13 @@ abstract class AbstractWordlist implements Wordlist, PwmService
     {
         if ( status() == STATUS.OPEN )
         {
-            return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ), getStatistics().asDebugMap() );
-        }
-        else
-        {
-            return new ServiceInfoBean( Collections.emptyList() );
+            return ServiceInfoBean.builder()
+                    .storageMethod( DataStorageMethod.LOCALDB )
+                    .debugProperties( getStatistics().asDebugMap() )
+                    .build();
         }
+
+        return ServiceInfoBean.builder().build();
     }
 
     WordlistStatistics getStatistics()
@@ -512,6 +513,6 @@ abstract class AbstractWordlist implements Wordlist, PwmService
     private BooleanSupplier makeProcessCancelSupplier( )
     {
         return () -> inhibitBackgroundImportFlag.get()
-                        || !STATUS.OPEN.equals( wlStatus );
+                || !STATUS.OPEN.equals( wlStatus );
     }
 }

+ 2 - 3
server/src/main/java/password/pwm/svc/wordlist/SharedHistoryManager.java

@@ -41,7 +41,6 @@ import password.pwm.util.secure.PwmRandom;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.TimerTask;
@@ -509,11 +508,11 @@ public class SharedHistoryManager implements PwmService
     {
         if ( status == STATUS.OPEN )
         {
-            return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ) );
+            return ServiceInfoBean.builder().storageMethod( DataStorageMethod.LOCALDB ).build();
         }
         else
         {
-            return new ServiceInfoBean( Collections.<DataStorageMethod>emptyList() );
+            return ServiceInfoBean.builder().build();
         }
     }
 }

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

@@ -28,7 +28,7 @@ import java.util.Set;
 
 public class FormMap implements Serializable, Map<String, String>
 {
-    private HashMap<String, String> backingMap = new HashMap<>();
+    private final HashMap<String, String> backingMap = new HashMap<>();
 
     public FormMap( )
     {

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

@@ -40,6 +40,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.i18n.Config;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.i18n.LocaleHelper;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.queue.SmsQueueManager;
@@ -207,7 +208,7 @@ public class LDAPPermissionCalculator implements Serializable
                             {
                                 for ( final UserPermission userPermission : userPermissions )
                                 {
-                                    if ( userPermission.getType() == UserPermission.Type.ldapGroup )
+                                    if ( userPermission.getType() == UserPermissionType.ldapGroup )
                                     {
                                         permissionRecords.add( new PermissionRecord( groupAttribute, pwmSetting, profile, permissionInfo.getAccess(), permissionInfo.getActor() ) );
                                     }

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

@@ -35,6 +35,7 @@ import password.pwm.config.value.UserPermissionValue;
 import password.pwm.config.value.X509CertificateValue;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmException;
+import password.pwm.ldap.permission.UserPermissionType;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.StringUtil;
 import password.pwm.util.secure.X509Utils;
@@ -263,7 +264,7 @@ public class PropertyConfigurationImporter
             if ( !StringUtil.isEmpty( value ) )
             {
                 permissions.add( UserPermission.builder()
-                        .type( UserPermission.Type.ldapQuery )
+                        .type( UserPermissionType.ldapQuery )
                         .ldapProfileID( LDAP_PROFILE )
                         .ldapQuery( filter )
                         .ldapBase( value )

+ 5 - 1
server/src/main/java/password/pwm/util/cli/commands/ResponseStatsCommand.java

@@ -165,9 +165,13 @@ public class ResponseStatsCommand extends AbstractCliCommand
         for ( final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values() )
         {
             final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine();
+            final TimeDuration searchTimeout = TimeDuration.of(
+                    Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT_MS ) ),
+                    TimeDuration.Unit.MILLISECONDS );
+
             final SearchConfiguration searchConfiguration = SearchConfiguration.builder()
                     .enableValueEscaping( false )
-                    .searchTimeout( Long.parseLong( pwmApplication.getConfig().readAppProperty( AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT ) ) )
+                    .searchTimeout( searchTimeout )
                     .username( "*" )
                     .enableValueEscaping( false )
                     .filter( ldapProfile.readSettingAsString( PwmSetting.LDAP_USERNAME_SEARCH_FILTER ) )

+ 7 - 5
server/src/main/java/password/pwm/util/db/DatabaseService.java

@@ -302,14 +302,16 @@ public class DatabaseService implements PwmService
             final DatabaseAboutProperty databaseAboutProperty = entry.getKey();
             debugProperties.put( databaseAboutProperty.name(), entry.getValue() );
         }
+
         if ( status() == STATUS.OPEN )
         {
-            return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.DB ), debugProperties );
-        }
-        else
-        {
-            return new ServiceInfoBean( Collections.emptyList(), debugProperties );
+            return ServiceInfoBean.builder()
+                    .storageMethod( DataStorageMethod.DB )
+                    .debugProperties( debugProperties )
+                    .build();
         }
+
+        return ServiceInfoBean.builder().debugProperties( debugProperties ).build();
     }
 
     public DatabaseAccessor getAccessor( )

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

@@ -25,6 +25,8 @@ import org.apache.commons.codec.binary.Base32;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.text.StringEscapeUtils;
 import password.pwm.PwmConstants;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.util.logging.PwmLogger;
 
 import java.io.IOException;
@@ -624,4 +626,21 @@ public abstract class StringUtil
 
         return false;
     }
+
+    public static void validateLdapSearchFilter( final String filter )
+            throws PwmUnrecoverableException
+    {
+        if ( filter == null || filter.isEmpty() )
+        {
+            return;
+        }
+
+        final int leftParens = StringUtils.countMatches( filter, "(" );
+        final int rightParens = StringUtils.countMatches( filter, ")" );
+
+        if ( leftParens != rightParens )
+        {
+            throw PwmUnrecoverableException.newException( PwmError.CONFIG_FORMAT_ERROR, "unbalanced parentheses in ldap filter" );
+        }
+    }
 }

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

@@ -27,7 +27,6 @@ import password.pwm.health.HealthRecord;
 import password.pwm.svc.PwmService;
 
 import java.io.Serializable;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -79,6 +78,10 @@ public class LocalDBService implements PwmService
                 returnInfo.put( entry.getKey(), String.valueOf( entry.getValue() ) );
             }
         }
-        return new ServiceInfoBean( Collections.singleton( DataStorageMethod.LOCALDB ), Collections.unmodifiableMap( returnInfo ) );
+
+        return ServiceInfoBean.builder()
+                .storageMethod( DataStorageMethod.LOCALDB )
+                .debugProperties( returnInfo )
+                .build();
     }
 }

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

@@ -22,7 +22,7 @@ package password.pwm.util.localdb;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Builder;
-import lombok.Getter;
+import lombok.Value;
 import password.pwm.PwmApplication;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
@@ -561,7 +561,7 @@ public final class WorkQueueProcessor<W extends Serializable>
         String convertToDebugString( W workItem );
     }
 
-    @Getter
+    @Value
     @Builder
     public static class Settings implements Serializable
     {

+ 4 - 3
server/src/main/java/password/pwm/util/logging/LocalDBLogger.java

@@ -41,7 +41,6 @@ import java.io.IOException;
 import java.text.NumberFormat;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Queue;
@@ -525,12 +524,14 @@ public class LocalDBLogger implements PwmService
         numberFormat.setMaximumFractionDigits( 3 );
         numberFormat.setMinimumFractionDigits( 3 );
 
-        return String.valueOf( this.getStoredEventCount() ) + " / " + maxEvents + " (" + numberFormat.format( percentFull ) + "%)";
+        return this.getStoredEventCount() + " / " + maxEvents + " (" + numberFormat.format( percentFull ) + "%)";
     }
 
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ) );
+        return ServiceInfoBean.builder()
+                .storageMethod( DataStorageMethod.LOCALDB )
+                .build();
     }
 
 }

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

@@ -51,11 +51,10 @@ import java.util.Map;
 
 public class ActionExecutor
 {
-
     private static final PwmLogger LOGGER = PwmLogger.forClass( ActionExecutor.class );
 
-    private PwmApplication pwmApplication;
-    private ActionExecutorSettings settings;
+    private final PwmApplication pwmApplication;
+    private final ActionExecutorSettings settings;
 
     private ActionExecutor( final PwmApplication pwmApplication, final ActionExecutorSettings settings )
     {
@@ -198,7 +197,8 @@ public class ActionExecutor
                 headers.put( HttpHeader.Authorization.getHttpName(), authHeaderValue );
             }
 
-            final HttpMethod method = HttpMethod.fromString( webAction.getMethod().toString() );
+            final HttpMethod method = HttpMethod.fromString( webAction.getMethod().toString() )
+                    .orElseThrow( () -> new IllegalStateException( "unaccepted http method" ) );
 
             final PwmHttpClientRequest clientRequest = PwmHttpClientRequest.builder()
                     .method( method )

+ 5 - 5
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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, queryMatch );
+                    final boolean match = UserPermissionTester.testUserPermission( pwmApplication, sessionLabel, userIdentity, queryMatch );
                     if ( match )
                     {
                         return profile;
@@ -611,13 +611,13 @@ public class CrService implements PwmService
             return false;
         }
 
-        if ( !LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_SETUP_RESPONSE ) ) )
+        if ( !UserPermissionTester.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 ( !LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession, userIdentity, config.readSettingAsUserPermission( PwmSetting.QUERY_MATCH_CHECK_RESPONSES ) ) )
+        if ( !UserPermissionTester.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;
@@ -669,6 +669,6 @@ public class CrService implements PwmService
         final LinkedHashSet<DataStorageMethod> usedStorageMethods = new LinkedHashSet<>();
         usedStorageMethods.addAll( ConfigurationUtil.getCrReadPreference( pwmApplication.getConfig() ) );
         usedStorageMethods.addAll( ConfigurationUtil.getCrWritePreference( pwmApplication.getConfig() ) );
-        return new ServiceInfoBean( Collections.unmodifiableList( new ArrayList<>( usedStorageMethods ) ) );
+        return ServiceInfoBean.builder().storageMethods( usedStorageMethods ).build();
     }
 }

+ 1 - 1
server/src/main/java/password/pwm/util/operations/OtpService.java

@@ -494,7 +494,7 @@ public class OtpService implements PwmService
 
     public ServiceInfoBean serviceInfo( )
     {
-        return new ServiceInfoBean( Collections.<DataStorageMethod>emptyList() );
+        return ServiceInfoBean.builder().build();
     }
 
     private static String readGuidIfNeeded(

+ 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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions( pwmApplication, pwmSession, userIdentity, userPermissions );
+                final boolean match = UserPermissionTester.testUserPermission( pwmApplication, pwmSession, userIdentity, userPermissions );
                 if ( match )
                 {
                     return loopPolicy;

+ 7 - 6
server/src/main/java/password/pwm/util/queue/SmsQueueManager.java

@@ -36,11 +36,11 @@ import password.pwm.health.HealthMessage;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.HttpHeader;
 import password.pwm.http.HttpMethod;
+import password.pwm.svc.PwmService;
 import password.pwm.svc.httpclient.PwmHttpClient;
 import password.pwm.svc.httpclient.PwmHttpClientConfiguration;
 import password.pwm.svc.httpclient.PwmHttpClientRequest;
 import password.pwm.svc.httpclient.PwmHttpClientResponse;
-import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.BasicAuthInfo;
@@ -296,14 +296,15 @@ public class SmsQueueManager implements PwmService
         {
             debugItems.putAll( workQueueProcessor.debugInfo() );
         }
+
         if ( status() == STATUS.OPEN )
         {
-            return new ServiceInfoBean( Collections.singletonList( DataStorageMethod.LOCALDB ), debugItems );
-        }
-        else
-        {
-            return new ServiceInfoBean( Collections.emptyList(), debugItems );
+            return ServiceInfoBean.builder()
+                    .storageMethod( DataStorageMethod.LOCALDB )
+                    .debugProperties( debugItems ).build();
         }
+
+        return ServiceInfoBean.builder().debugProperties( debugItems ).build();
     }
 
     private List<String> splitMessage( final String input )

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

@@ -33,7 +33,7 @@ import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 import password.pwm.ldap.auth.AuthenticationResult;
 import password.pwm.ldap.auth.SimpleLdapAuthenticator;
 import password.pwm.ldap.search.UserSearchEngine;
@@ -97,7 +97,7 @@ public class RestAuthenticationProcessor
             {
                 {
                     final List<UserPermission> userPermission = pwmApplication.getConfig().readSettingAsUserPermission( PwmSetting.WEBSERVICES_QUERY_MATCH );
-                    final boolean result = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, userPermission );
+                    final boolean result = UserPermissionTester.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 = LdapPermissionTester.testUserPermissions( pwmApplication, sessionLabel, userIdentity, userPermission );
+                    thirdParty = UserPermissionTester.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.LdapPermissionTester;
+import password.pwm.ldap.permission.UserPermissionTester;
 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 = LdapPermissionTester.testUserPermissions(
+            final boolean result = UserPermissionTester.testUserPermission(
                     restRequest.getPwmApplication(),
                     restRequest.getSessionLabel(),
                     targetUserIdentity.getUserIdentity(),

+ 15 - 19
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -246,7 +246,7 @@
     <setting hidden="false" key="accountInfo.queryMatch" level="2" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="display.passwordHistory" level="1" required="true">
@@ -413,7 +413,7 @@
     <setting hidden="false" key="password.allowChange.queryMatch" level="2" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="logoutAfterPasswordChange" level="1" required="true">
@@ -544,10 +544,6 @@
     </setting>
     <setting hidden="false" key="pwmAdmin.queryMatch" level="0" required="true">
         <ldapPermission actor="proxy" access="read"/>
-        <example>cn=@PwmAppName@-Admins,ou=Groups,o=example</example>
-        <example template="AD">CN=@PwmAppName@-Administrators,CN=Builtin,DC=site,DC=example,DC=net</example>
-        <example template="ORACLE_DS">cn=@PwmAppName@-Admins,OU=Groups,DC=ad,DC=site,DC=example,DC=net</example>
-        <example template="OPEN_LDAP">cn=@PwmAppName@-Admins,dc=example,dc=com</example>
         <default>
             <value/>
         </default>
@@ -1114,7 +1110,7 @@
     <setting hidden="false" key="password.policy.queryMatch" level="1">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="password.policy.source" level="1" required="true">
@@ -1906,7 +1902,7 @@
     <setting hidden="false" key="otp.secret.allowSetup.queryMatch" level="1" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="otp.secret.identifier" level="2" required="true">
@@ -2422,13 +2418,13 @@
     <setting hidden="false" key="challenge.allowSetup.queryMatch" level="2" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="command.checkResponses.queryMatch" level="2" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="true" key="challenge.profile.list" level="1">
@@ -2443,7 +2439,7 @@
     <setting hidden="false" key="challenge.policy.queryMatch" level="1">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="recovery.verificationMethods" level="1">
@@ -3061,7 +3057,7 @@
     <setting hidden="false" key="updateAttributes.queryMatch" level="1" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=person)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="updateAttributes.writeAttributes" level="1">
@@ -3201,7 +3197,7 @@
     <setting hidden="false" key="peopleSearch.queryMatch" level="1" required="true">
         <ldapPermission actor="self_other" access="read"/>
         <default syntaxVersion="2">
-            <value>{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}</value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="true" key="peopleSearch.searchAttributes" level="1" required="false">
@@ -3330,7 +3326,7 @@
     </setting>
     <setting hidden="false" key="peopleSearch.photo.queryFilter" level="2">
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="peopleSearch.idleTimeout" level="1">
@@ -3743,13 +3739,13 @@
     <setting hidden="false" key="recovery.queryMatch" level="1" required="true">
         <ldapPermission actor="proxy" access="read"/>
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
         <default syntaxVersion="2" template="AD">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
         <default syntaxVersion="2" template="ORACLE_DS">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="helpdesk.token.sendMethod" level="1">
@@ -3900,7 +3896,7 @@
     </setting>
     <setting hidden="false" key="reporting.ldap.userMatch" level="1">
         <default syntaxVersion="2">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="reporting.ldap.maxQuerySize" level="2">
@@ -4008,7 +4004,7 @@
             <value/>
         </default>
         <default syntaxVersion="2" template="NOVL_IDM">
-            <value><![CDATA[{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}]]></value>
+            <value>{type:"ldapAllUsers","ldapProfileID":"all"}</value>
         </default>
     </setting>
     <setting hidden="false" key="webservices.thirdParty.queryMatch" level="2">

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

@@ -36,6 +36,7 @@ Button_Previous=Previous
 Button_CheckSettings=Check Settings
 Button_ShowAdvanced=Show %1% Advanced Settings
 Button_HideAdvanced=Hide Advanced Settings
+Button_AddPermission=Add Users
 Confirm_ConfigPasswordStored=The configuration password has been changed; please click the "save" icon to store the new password.
 Confirm_RemoveProfile=Are you sure you want to remove the profile <code>%1%</code>?  The setting values associated with this profile will also be removed.
 Confirm_LockConfig=Are you sure you want to restrict the configuration?  After you restrict the configuration, you must authenticate using your LDAP directory credentials before authenticating, so be sure your LDAP configuration is working properly before restricting.
@@ -54,7 +55,7 @@ Display_ConfigManagerNew=<p><b>Welcome to %1%.</b>  We hope you enjoy using this
 Display_ConfigManagerRunning=<p><b>The configuration for this server has been restricted.  However you can still edit the configuration.</b></p><p>For security reasons, to edit the configuration, you must upload (and then download) the <em>%1%</em> file.</p>
 Display_ConfigManagerRunningEditor=Your modified configuration is currently in memory, but has not yet been saved.  Please choose an option below to continue.
 Display_ConfigOpenInfo=<p>@PwmAppName@ is currently in <b>configuration</b> mode.   This mode allows updating the configuration without authenticating to an LDAP directory first.  End user functionality is not available in this mode.</p><p>After you have verified the LDAP directory settings, use the Configuration Manager to restrict the configuration to prevent unauthorized changes.  After restricting, the configuration can still be changed but will require LDAP directory authentication first.</p>
-Display_EditorLDAPSizeExceeded=Search results exceeded maximum search size.  Only this display is affected by this restriction.  Application behavior is not constrained by this search limit so additional users will match beyond those shown here.
+Display_EditorLDAPSizeExceeded=Search results exceeded maximum search size.  Only this display is affected by these search limits.  Application behavior is not constrained by this search limit so additional users will match beyond those shown here.
 Display_LdapPermissionRecommendations=<p>This report shows recommended LDAP permission requirements for the current configuration.  Depending on your LDAP directory type, these may be referred to as permissions, rights, or ACLs (Access Control List).</p><p?>These recommendations should be applied with caution and with an understanding of the security model of your specific LDAP directory environment.  <b>The suggested permissions may not neccessarily be appropriate for your environment.</b>  The access levels <i>read</i> and <i>write</i> are generalizations.  Your LDAP directory may use different permission types.</p><p>There may be additional permissions required that do not appear on this report. For example, permissions required to resolve macro expressions are not included.</p>
 Display_Wordlists_Description=Word lists and seed lists can be uploaded using this page or configured to import from a remote URL using the settings <code>@PwmSettingReference:pwm.wordlist.location@</code> and <code>@PwmSettingReference:pwm.seedlist.location@</code>.
 Display_SettingFilter_Level_0=Required
@@ -88,6 +89,7 @@ MenuDisplay_CancelConfig=Are you sure you want to abandon the changes to the cur
 MenuDisplay_StartConfigGuide=Start the guide to configure your LDAP server, certificates and basic settings.
 MenuDisplay_UploadConfig=Upload an existing configuration file. The uploaded file will be saved as the new configuration and will replace the current configuration.
 MenuDisplay_ViewLog=View recent log events. Requires pop-up windows to be enabled in your browser.
+MenuDisplay_Permissions=Select the matching set of users.  Multiple types of matches can be configured.  Each match type is additive, so that any user that matches any item is considered to be included.
 MenuItem_AlternateNewConfig=Alternate Option: Edit a new configuration
 MenuItem_AlternateUnlockConfig=Alternate Option: Un-Locking the Configuration
 MenuItem_CancelEdits=Cancel Edits
@@ -102,10 +104,20 @@ MenuItem_StartConfigGuide=Start Configuration Guide
 MenuItem_UploadConfig=Import Configuration
 MenuItem_UnlockConfig=Open Configuration
 MenuItem_ViewLog=View Log
+MenuItem_Permission_AllUsers=All Users
+MenuItem_Permission_LdapUser=LDAP User
+MenuItem_Permission_LdapGroup=LDAP Group
+MenuItem_Permission_LdapFilter=LDAP Search Filter
+MenuDisplay_Permission_AllUsers=Include all users of all profiles or a specified LDAP profile.
+MenuDisplay_Permission_LdapUser=Select a specific user based on the user's LDAP DN.
+MenuDisplay_Permission_LdapGroup=Select a set of users based on membership in an LDAP Group.
+MenuDisplay_Permission_LdapFilter=Include users that match a specified LDAP search filter.
 Setting_Permission_Profile=LDAP Profile
+Setting_Permission_Profile_AllUsers=All Users in Profile
 Setting_Permission_Filter=LDAP Search Filter
 Setting_Permission_Base=LDAP Base DN (Optional)
 Setting_Permission_Base_Group=LDAP Group DN
+Setting_Permission_Base_User=LDAP User DN
 Title_ConfigGuide=Configuration Guide
 Title_ConfigGuide_app=Application Configuration
 Title_ConfigGuide_ldap=LDAP Configuration
@@ -148,7 +160,7 @@ Tooltip_IconSearchNoResults=No search results
 Tooltip_IconExpandAll=Expand All
 Tooltip_IconCollapseAll=Collapse All
 Tooltip_IconFilterSettings=Filter Settings
-Tooltip_Setting_Permission_Profile=Specify which of the defined LDAP profiles to use for the associated filter.  If "all", then all profiles will be checked for the associated filter.
+Tooltip_Setting_Permission_Profile=Specify which of the defined LDAP profiles to use for the permission.  If "all", then all profiles will be checked for the permission.
 Tooltip_Setting_Permission_Filter=A valid LDAP search filter.
 Tooltip_Setting_Permission_Base=An optional LDAP Base DN for the search.  If supplied, only users under this LDAP Base DN will be considered a match.
 Tooltip_FormOptions_Description=Detailed description of this form item, including any special instructions.

+ 58 - 0
server/src/test/java/password/pwm/config/stored/StoredConfigurationTest.java

@@ -0,0 +1,58 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package password.pwm.config.stored;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import password.pwm.PwmApplication;
+import password.pwm.config.Configuration;
+import password.pwm.util.localdb.TestHelper;
+
+import java.io.InputStream;
+
+public class StoredConfigurationTest
+{
+    @Rule
+    public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+    private static Configuration configuration;
+
+    @BeforeClass
+    public static void setUp() throws Exception
+    {
+        try ( InputStream xmlFile = ConfigurationCleanerTest.class.getResourceAsStream( "ConfigurationCleanerTest.xml" ); )
+        {
+            final StoredConfiguration storedConfiguration = StoredConfigurationFactory.input( xmlFile );
+            configuration = new Configuration( storedConfiguration );
+        }
+    }
+
+    @Test
+    public void configurationHashTest()
+            throws Exception
+    {
+        final PwmApplication pwmApplication = TestHelper.makeTestPwmApplication( temporaryFolder.newFolder(), configuration );
+        final String configHash = configuration.configurationHash( pwmApplication.getSecureService() );
+        System.out.println( configHash );
+    }
+}

+ 1 - 0
webapp/src/main/webapp/WEB-INF/jsp/configeditor.jsp

@@ -164,6 +164,7 @@
 <pwm:script-ref url="/public/resources/js/configeditor-settings-challenges.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-customlink.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-remotewebservices.js"/>
+<pwm:script-ref url="/public/resources/js/configeditor-settings-permissions.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>
 

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

@@ -0,0 +1,304 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2020 The PWM Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// -------------------------- user permission handler ------------------------------------
+
+var UserPermissionHandler = {};
+UserPermissionHandler.defaultFilterValue = {type:'ldapFilter',ldapQuery:"(objectClass=*)",ldapBase:""};
+UserPermissionHandler.defaultGroupValue = {type:'ldapGroup',ldapBase:""};
+UserPermissionHandler.defaultUserValue = {type:'ldapUser',ldapBase:""};
+UserPermissionHandler.defaultAllUserValue = {type:'ldapAllUsers',ldapProfileID:'all'};
+
+UserPermissionHandler.init = function(keyName) {
+    console.log('UserPermissionHandler init for ' + keyName);
+    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
+        PWM_VAR['clientSettingCache'][keyName] = resultValue;
+        UserPermissionHandler.draw(keyName);
+    });
+};
+
+UserPermissionHandler.draw = function(keyName) {
+    var resultValue = PWM_VAR['clientSettingCache'][keyName];
+    var parentDiv = 'table_setting_' + keyName;
+    var parentDivElement = PWM_MAIN.getObject(parentDiv);
+
+    while (parentDivElement.firstChild) {
+        parentDivElement.removeChild(parentDivElement.firstChild);
+    }
+
+    var htmlBody = '';
+    if (resultValue.length > 0) {
+        for (var iteration in resultValue) {
+            (function (rowKey) {
+                if (htmlBody.length > 0) {
+                    htmlBody += '<br/><br/><div style="clear:both; text-align:center">OR</span></div>'
+                }
+                htmlBody += UserPermissionHandler.drawRow(keyName, resultValue, rowKey)
+            }(iteration));
+        }
+    } else {
+        htmlBody += '<div><p>No users are added.</p></div>';
+    }
+
+    htmlBody += '<button class="btn" id="button-' + keyName + '-addPermission">'
+        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + PWM_CONFIG.showString('Button_AddPermission') + '</button>';
+
+    var hideMatch = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideMatch')
+        || resultValue.length === 0;
+    if (!hideMatch) {
+        htmlBody += '<button id="button-' + keyName + '-viewMatches" class="btn">'
+            + '<span class="btn-icon pwm-icon pwm-icon-users"></span>View Matches</button>';
+    }
+
+    parentDivElement.innerHTML = htmlBody;
+
+    for (var iteration in resultValue) {
+        (function(rowKey) {
+            UserPermissionHandler.addRowHandlers(resultValue, keyName, rowKey);
+        }(iteration));
+    }
+
+    PWM_MAIN.addEventHandler('button-' + keyName + '-viewMatches','click',function(){
+        var dataHandler = function(data) {
+            var html = PWM_CONFIG.convertListOfIdentitiesToHtml(data['data']);
+            PWM_MAIN.showDialog({title:'Matches',text:html,dialogClass:'wide',showOk:false,showClose:true});
+        };
+        PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.config.function.UserMatchViewerFunction', dataHandler, null)
+    });
+
+    PWM_MAIN.addEventHandler('button-' + keyName + '-addPermission','click',function(){
+        UserPermissionHandler.addPermission(keyName);
+    });
+};
+
+UserPermissionHandler.drawRow = function(keyName, resultValue, rowKey) {
+    var inputID = "value-" + keyName + "-" + rowKey;
+    var type = resultValue[rowKey]['type'];
+
+    var htmlBody = '<div class="setting_item_value_wrapper" style="float:left; width: 560px;">';
+    var profileLabelKey = (type === 'ldapAllUsers') ? 'Setting_Permission_Profile_AllUsers' : 'Setting_Permission_Profile';
+
+    htmlBody += '<table class="noborder">'
+        + '<tr><td style="width:200px" id="' + inputID + '_profileHeader' + '">' + PWM_CONFIG.showString(profileLabelKey) + '</td>'
+        + '<td></td>'
+        + '<td><div class="configStringPanel permissionValuePanel">'
+        + '<table class="noborder"><tr><td><select name="' + inputID + '-profileSelect" id="' + inputID + '-profileSelect">'
+        + '<option value="all">All Profiles</option><option value="specified">Specified Profile</option>'
+        + '</select></td><td>'
+        + '<div class="" id="' + inputID + '-profileWrapper">'
+        + '<input  class="configStringInput permissionProfileValueInput" id="' + inputID + '-profile" list="' + inputID + '-datalist"/>'
+        + '</div></td></tr></table>'
+        + '<datalist id="' + inputID + '-datalist"/>'
+        + '</div>'
+        + '</td>'
+        + '</tr>';
+
+    if (type !== 'ldapAllUsers') {
+        if (type !== 'ldapGroup' && type !== 'ldapUser') {
+            htmlBody += '<tr>'
+                + '<td><span id="' + inputID + '_FilterHeader' + '">' + PWM_CONFIG.showString('Setting_Permission_Filter') + '</span></td>'
+                + '<td id="icon-edit-query-' + inputID + '"><div title="Edit Value" class="btn-icon pwm-icon pwm-icon-edit"></div></td>'
+                + '<td><div class="configStringPanel noWrapTextBox border permissionValuePanel" id="' + inputID + '-query"></div></td>'
+                + '</tr>';
+        }
+
+        var rowLabelKey = (type === 'ldapGroup') ? 'Setting_Permission_Base_Group' :
+            (type === 'ldapUser') ? 'Setting_Permission_Base_User' : 'Setting_Permission_Base'
+        htmlBody += '<tr>'
+            + '<td><span id="' + inputID + '_BaseHeader' + '">'
+            + PWM_CONFIG.showString(rowLabelKey)
+            + '</span></td>'
+            + '<td id="icon-edit-base-' + inputID + '"><div title="Edit Value" class="btn-icon pwm-icon pwm-icon-edit"></div></td>'
+            + '<td><div class="configStringPanel noWrapTextBox border permissionValuePanel" id="' + inputID + '-base">&nbsp;</div></td>'
+            + '</tr>';
+    }
+
+    htmlBody += '</table></div>';
+    htmlBody += '<div id="button-' + inputID + '-deleteRow" style="float:right" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></div>';
+
+    return htmlBody;
+};
+
+UserPermissionHandler.addRowHandlers = function( resultValue, keyName, rowKey) {
+    var inputID = "value-" + keyName + "-" + rowKey;
+
+    var profileDataList = PWM_MAIN.getObject(inputID + "-datalist");
+    var profileIdList = PWM_SETTINGS['var']['ldapProfileIds'];
+    for (var i in profileIdList) {
+        var option = document.createElement('option');
+        option.value = profileIdList[i];
+        profileDataList.appendChild(option);
+    }
+
+    var currentProfile = PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'];
+    currentProfile = currentProfile === undefined ? '' : currentProfile;
+    var profileSelectNewValueFunction = function(newValue, writeNewValue) {
+        var allProfilesEnabled = !newValue || newValue === 'all' || newValue === '';
+        if (allProfilesEnabled) {
+            PWM_MAIN.JSLibrary.setValueOfSelectElement(inputID + '-profileSelect', 'all');
+            PWM_MAIN.addCssClass( inputID + '-profileWrapper', 'hidden');
+        } else {
+            PWM_MAIN.JSLibrary.setValueOfSelectElement(inputID + '-profileSelect', 'specified');
+            PWM_MAIN.removeCssClass( inputID + '-profileWrapper', 'hidden');
+        }
+
+        if ( writeNewValue ) {
+            if (allProfilesEnabled) {
+                PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = '';
+            }
+            UserPermissionHandler.write(keyName);
+        }
+    }
+    profileSelectNewValueFunction(currentProfile, false);
+    PWM_MAIN.getObject(inputID+'-profile').value = currentProfile;
+
+    PWM_MAIN.addEventHandler(inputID + '-profileSelect','change',function(){
+        profileSelectNewValueFunction(this.value, true);
+    });
+
+    PWM_MAIN.addEventHandler(inputID + '-profile','input',function(){
+        PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = this.value;
+        UserPermissionHandler.write(keyName);
+    });
+
+    if (resultValue[rowKey]['type'] !== 'ldapGroup') {
+        UILibrary.addTextValueToElement(inputID + '-query', PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery']);
+        var queryEditor = function(){
+            UILibrary.stringEditorDialog({
+                value:PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'],
+                completeFunction:function(value) {
+                    PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'] = value;
+                    UserPermissionHandler.write(keyName,true);
+                }
+            });
+        };
+
+        PWM_MAIN.addEventHandler(inputID + "-query",'click',function(){
+            queryEditor();
+        });
+        PWM_MAIN.addEventHandler('icon-edit-query-' + inputID,'click',function(){
+            queryEditor();
+        });
+    }
+
+    var currentBaseValue = ('ldapBase' in resultValue[rowKey]) ? resultValue[rowKey]['ldapBase'] : "";
+    var baseEditor = function(){
+        UILibrary.editLdapDN(function(value, ldapProfileID) {
+            PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ldapProfileID;
+            PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value;
+            UserPermissionHandler.write(keyName,true);
+        }, {currentDN: currentBaseValue});
+    };
+    if (currentBaseValue && currentBaseValue.length > 0) {
+        UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue);
+    }
+    PWM_MAIN.addEventHandler(inputID + '-base','click',function(){
+        baseEditor();
+    });
+    PWM_MAIN.addEventHandler('icon-edit-base-' + inputID,'click',function(){
+        baseEditor();
+    });
+
+    var deleteButtonID = 'button-' + inputID + '-deleteRow';
+    var hasID = PWM_MAIN.getObject(deleteButtonID) ? "YES" : "NO";
+    console.log("addEventHandler row: " + deleteButtonID + " rowKey=" + rowKey + " hasID="+hasID);
+    PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){
+        console.log("delete row: " + inputID + " rowKey=" + rowKey + " hasID="+hasID);
+        delete PWM_VAR['clientSettingCache'][keyName][rowKey];
+        UserPermissionHandler.write(keyName,true);
+    });
+
+    PWM_MAIN.showTooltip({
+        id:inputID +'_profileHeader',
+        width: 300,
+        text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Profile')
+    });
+    PWM_MAIN.showTooltip({
+        id:inputID +'_FilterHeader',
+        width: 300,
+        text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Filter')
+    });
+    PWM_MAIN.showTooltip({
+        id: inputID + '_BaseHeader',
+        width: 300,
+        text: PWM_CONFIG.showString('Tooltip_Setting_Permission_Base')
+    });
+}
+
+UserPermissionHandler.write = function(settingKey,redraw) {
+    var nextFunction = function(){
+        if (redraw) {
+            UserPermissionHandler.draw(settingKey);
+        }
+    };
+    PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey], nextFunction);
+};
+
+UserPermissionHandler.addPermission = function(keyName) {
+    var bodyHtml = '<div><p>' + PWM_CONFIG.showString('MenuDisplay_Permissions') + '</p></div><table class="">'
+    var hideGroup = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideGroups');
+    bodyHtml += '<tr><td><button class="btn" id="button-' + keyName + '-addAllUsersValue">'
+        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + PWM_CONFIG.showString('MenuItem_Permission_AllUsers')
+        + '</button></td><td>' + PWM_CONFIG.showString('MenuDisplay_Permission_AllUsers') + '</td></tr>';
+    bodyHtml += '<tr><td><button class="btn" id="button-' + keyName + '-addUserValue">'
+        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + PWM_CONFIG.showString('MenuItem_Permission_LdapUser')
+        + '</button></td><td>' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapUser') + '</td></tr>';
+    if (!hideGroup) {
+        bodyHtml += '<tr><td><button class="btn" id="button-' + keyName + '-addGroupValue">'
+            + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + PWM_CONFIG.showString('MenuItem_Permission_LdapGroup')
+            + '</button></td><td>' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapGroup') + '</td></tr>';
+    }
+    bodyHtml += '<tr><td><button class="btn" id="button-' + keyName + '-addFilterValue">'
+        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>' + PWM_CONFIG.showString('MenuItem_Permission_LdapFilter')
+        + '</button></td><td>' + PWM_CONFIG.showString('MenuDisplay_Permission_LdapFilter') + '</td></tr>';
+
+    bodyHtml += '</table>';
+
+    var completeAddPermission = function(template) {
+        PWM_VAR['clientSettingCache'][keyName].push(PWM_MAIN.copyObject(template));
+        PWM_MAIN.closeWaitDialog();
+        UserPermissionHandler.write(keyName, true);
+    }
+
+    var dialogOptions = {};
+    dialogOptions.title = PWM_CONFIG.showString('Button_AddPermission');
+    dialogOptions.text = bodyHtml;
+    dialogOptions.showCancel = false;
+    dialogOptions.showClose = true;
+    dialogOptions.showOk = false;
+    dialogOptions.loadFunction = function() {
+        PWM_MAIN.addEventHandler('button-' + keyName + '-addFilterValue','click',function(){
+            completeAddPermission(UserPermissionHandler.defaultFilterValue);
+        });
+
+        PWM_MAIN.addEventHandler('button-' + keyName + '-addGroupValue','click',function(){
+            completeAddPermission(UserPermissionHandler.defaultGroupValue);
+        });
+
+        PWM_MAIN.addEventHandler('button-' + keyName + '-addUserValue','click',function(){
+            completeAddPermission(UserPermissionHandler.defaultUserValue);
+        });
+
+        PWM_MAIN.addEventHandler('button-' + keyName + '-addAllUsersValue','click',function(){
+            completeAddPermission(UserPermissionHandler.defaultAllUserValue);
+        });
+    };
+    PWM_MAIN.showDialog(dialogOptions);
+}

+ 20 - 224
webapp/src/main/webapp/public/resources/js/configeditor-settings.js

@@ -877,202 +877,6 @@ BooleanHandler.toggle = function(keyName,widget) {
 };
 
 
-// -------------------------- user permission handler ------------------------------------
-
-var UserPermissionHandler = {};
-UserPermissionHandler.defaultFilterValue = {type:'ldapFilter',ldapQuery:"(objectClass=*)",ldapBase:""};
-UserPermissionHandler.defaultGroupValue = {type:'ldapGroup',ldapBase:""};
-
-UserPermissionHandler.init = function(keyName) {
-    console.log('UserPermissionHandler init for ' + keyName);
-    PWM_CFGEDIT.readSetting(keyName, function(resultValue) {
-        PWM_VAR['clientSettingCache'][keyName] = resultValue;
-        UserPermissionHandler.draw(keyName);
-    });
-};
-
-UserPermissionHandler.draw = function(keyName) {
-    var resultValue = PWM_VAR['clientSettingCache'][keyName];
-    var parentDiv = 'table_setting_' + keyName;
-    var parentDivElement = PWM_MAIN.getObject(parentDiv);
-
-    while (parentDivElement.firstChild) {
-        parentDivElement.removeChild(parentDivElement.firstChild);
-    }
-
-    var htmlBody = '';
-    for (var iteration in resultValue) {
-        (function(rowKey) {
-            var inputID = "value-" + keyName + "-" + rowKey;
-
-            if (htmlBody.length > 0) {
-                htmlBody += '<br/><br/><div style="clear:both; text-align:center">OR</span></div>'
-            }
-
-            htmlBody += '<div class="setting_item_value_wrapper" style="float:left; width: 570px;"><div style="width:100%; text-align:center">';
-
-            var currentProfileValue = ('ldapProfileID' in resultValue[rowKey]) ? resultValue[rowKey]['ldapProfileID'] : "";
-            htmlBody += '</div><table class="noborder">'
-                + '<td style="width:200px" id="' + inputID + '_profileHeader' + '">' + PWM_CONFIG.showString('Setting_Permission_Profile') + '</td>'
-                + '<td></td>'
-                + '<td><input style="width: 200px;" class="configStringInput" id="' + inputID + '-profile" list="' + inputID + '-datalist" value="' +  currentProfileValue + '"/>'
-                + '<datalist id="' + inputID + '-datalist"/></td>'
-                + '</tr>';
-
-            if (resultValue[rowKey]['type'] !== 'ldapGroup') {
-                htmlBody += '<tr>'
-                    + '<td><span id="' + inputID + '_FilterHeader' + '">' + PWM_CONFIG.showString('Setting_Permission_Filter') + '</span></td>'
-                    + '<td id="icon-edit-query-' + inputID + '"><div title="Edit Value" class="btn-icon pwm-icon pwm-icon-edit"></div></td>'
-                    + '<td><div style="width: 350px; padding: 5px;" class="configStringPanel noWrapTextBox border" id="' + inputID + '-query"></div></td>'
-                    + '</tr>';
-            }
-
-            htmlBody += '<tr>'
-                + '<td><span id="' + inputID + '_BaseHeader' + '">'
-                + PWM_CONFIG.showString((resultValue[rowKey]['type'] === 'ldapGroup') ?  'Setting_Permission_Base_Group' : 'Setting_Permission_Base')
-                + '</span></td>'
-                + '<td id="icon-edit-base-' + inputID + '"><div title="Edit Value" class="btn-icon pwm-icon pwm-icon-edit"></div></td>'
-                + '<td><div style="width: 350px; padding: 5px;" class="configStringPanel noWrapTextBox border" id="' + inputID + '-base">&nbsp;</div></td>'
-                + '</tr>';
-
-
-            htmlBody += '</table></div><div id="button-' + inputID + '-deleteRow" style="float:right" class="delete-row-icon action-icon pwm-icon pwm-icon-times"></div>';
-        }(iteration));
-    }
-    parentDivElement.innerHTML = parentDivElement.innerHTML + htmlBody;
-
-    setTimeout(function(){
-        for (var iteration in resultValue) {
-            (function(rowKey) {
-                var inputID = "value-" + keyName + "-" + rowKey;
-                console.log('inputID-' + inputID);
-
-                var profileSelectElement = PWM_MAIN.getObject(inputID + "-datalist");
-                profileSelectElement.appendChild(new Option('all'));
-                var profileIdList = PWM_SETTINGS['var']['ldapProfileIds'];
-                for (var i in profileIdList) {
-                    profileSelectElement.appendChild(new Option(profileIdList[i]));
-                }
-
-                PWM_MAIN.addEventHandler(inputID + '-profile','input',function(){
-                    console.log('write');
-                    PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = this.value;
-                    UserPermissionHandler.write(keyName);
-                });
-
-                if (resultValue[rowKey]['type'] !== 'ldapGroup') {
-                    UILibrary.addTextValueToElement(inputID + '-query', PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery']);
-                    var queryEditor = function(){
-                        UILibrary.stringEditorDialog({
-                            value:PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'],
-                            completeFunction:function(value) {
-                                PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'] = value;
-                                UserPermissionHandler.write(keyName,true);
-                            }
-                        });
-                    };
-
-                    PWM_MAIN.addEventHandler(inputID + "-query",'click',function(){
-                        queryEditor();
-                    });
-                    PWM_MAIN.addEventHandler('icon-edit-query-' + inputID,'click',function(){
-                        queryEditor();
-                    });
-                }
-
-                var currentBaseValue = ('ldapBase' in resultValue[rowKey]) ? resultValue[rowKey]['ldapBase'] : "";
-                var baseEditor = function(){
-                    UILibrary.editLdapDN(function(value, ldapProfileID) {
-                        PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ldapProfileID;
-                        PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value;
-                        UserPermissionHandler.write(keyName,true);
-                    }, {currentDN: currentBaseValue});
-                };
-                if (currentBaseValue && currentBaseValue.length > 0) {
-                    UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue);
-                }
-                PWM_MAIN.addEventHandler(inputID + '-base','click',function(){
-                    baseEditor();
-                });
-                PWM_MAIN.addEventHandler('icon-edit-base-' + inputID,'click',function(){
-                    baseEditor();
-                });
-
-
-                var deleteButtonID = 'button-' + inputID + '-deleteRow';
-                var hasID = PWM_MAIN.getObject(deleteButtonID) ? "YES" : "NO";
-                console.log("addEventHandler row: " + deleteButtonID + " rowKey=" + rowKey + " hasID="+hasID);
-                PWM_MAIN.addEventHandler(deleteButtonID,'click',function(){
-                    console.log("delete row: " + inputID + " rowKey=" + rowKey + " hasID="+hasID);
-                    delete PWM_VAR['clientSettingCache'][keyName][rowKey];
-                    UserPermissionHandler.write(keyName,true);
-                });
-
-                PWM_MAIN.showTooltip({
-                    id:inputID +'_profileHeader',
-                    width: 300,
-                    text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Profile')
-                });
-                PWM_MAIN.showTooltip({
-                    id:inputID +'_FilterHeader',
-                    width: 300,
-                    text:PWM_CONFIG.showString('Tooltip_Setting_Permission_Filter')
-                });
-                PWM_MAIN.showTooltip({
-                    id: inputID + '_BaseHeader',
-                    width: 300,
-                    text: PWM_CONFIG.showString('Tooltip_Setting_Permission_Base')
-                });
-            }(iteration));
-        }
-    },10);
-
-    var options = PWM_SETTINGS['settings'][keyName]['options'];
-
-    var buttonRowHtml = '<button class="btn" id="button-' + keyName + '-addFilterValue">'
-        + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Filter</button>';
-
-    var hideGroup = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideGroups');
-    if (!hideGroup) {
-        buttonRowHtml += '<button class="btn" id="button-' + keyName + '-addGroupValue">'
-            + '<span class="btn-icon pwm-icon pwm-icon-plus-square"></span>Add Group</button>';
-    }
-
-    var hideMatch = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'], 'Permission_HideMatch');
-    if (!hideMatch) {
-        buttonRowHtml += '<button id="button-' + keyName + '-viewMatches" class="btn">'
-            + '<span class="btn-icon pwm-icon pwm-icon-user"></span>View Matches</button>';
-    }
-
-    parentDivElement.innerHTML = parentDivElement.innerHTML + buttonRowHtml;
-
-    PWM_MAIN.addEventHandler('button-' + keyName + '-viewMatches','click',function(){
-        var dataHandler = function(data) {
-            var html = PWM_CONFIG.convertListOfIdentitiesToHtml(data['data']);
-            PWM_MAIN.showDialog({title:'Matches',text:html});
-        };
-        PWM_CFGEDIT.executeSettingFunction(keyName, 'password.pwm.config.function.UserMatchViewerFunction', dataHandler, null)
-    });
-
-    PWM_MAIN.addEventHandler('button-' + keyName + '-addFilterValue','click',function(){
-        PWM_VAR['clientSettingCache'][keyName].push(PWM_MAIN.copyObject(UserPermissionHandler.defaultFilterValue));
-        UserPermissionHandler.write(keyName, true);
-    });
-
-    PWM_MAIN.addEventHandler('button-' + keyName + '-addGroupValue','click',function(){
-        PWM_VAR['clientSettingCache'][keyName].push(PWM_MAIN.copyObject(UserPermissionHandler.defaultGroupValue));
-        UserPermissionHandler.write(keyName, true);
-    });
-};
-
-UserPermissionHandler.write = function(settingKey,redraw) {
-    var nextFunction = function(){
-        if (redraw) {
-            UserPermissionHandler.draw(settingKey);
-        }
-    };
-    PWM_CFGEDIT.writeSetting(settingKey, PWM_VAR['clientSettingCache'][settingKey], nextFunction);
-};
 
 // -------------------------- option list handler ------------------------------------
 
@@ -1106,36 +910,28 @@ OptionListHandler.init = function(keyName) {
 
 OptionListHandler.draw = function(keyName) {
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
-    require(["dojo/_base/array"],function(array){
-        var options = PWM_SETTINGS['settings'][keyName]['options'];
-        for (var key in options) {
-            (function (optionKey) {
-                var buttonID = keyName + "_button_" + optionKey;
-                var checked = array.indexOf(resultValue,optionKey) > -1;
-                PWM_MAIN.getObject(buttonID).checked = checked;
-                PWM_MAIN.getObject(buttonID).disabled = false;
-                PWM_MAIN.addEventHandler(buttonID,'change',function(){
-                    OptionListHandler.toggle(keyName,optionKey);
-                });
-            })(key);
-        }
-    });
+    var options = PWM_SETTINGS['settings'][keyName]['options'];
+    for (var key in options) {
+        (function (optionKey) {
+            var buttonID = keyName + "_button_" + optionKey;
+            var checked = PWM_MAIN.JSLibrary.arrayContains(resultValue,optionKey)
+            PWM_MAIN.getObject(buttonID).checked = checked;
+            PWM_MAIN.getObject(buttonID).disabled = false;
+            PWM_MAIN.addEventHandler(buttonID,'change',function(){
+                OptionListHandler.toggle(keyName,optionKey);
+            });
+        })(key);
+    }
 };
 
 OptionListHandler.toggle = function(keyName,optionKey) {
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
-    require(["dojo/_base/array"],function(array){
-        var checked = array.indexOf(resultValue,optionKey) > -1;
-        if (checked) {
-            var index = array.indexOf(resultValue, optionKey);
-            while (index > -1) {
-                resultValue.splice(index, 1);
-                index = array.indexOf(resultValue, optionKey);
-            }
-        } else {
-            resultValue.push(optionKey);
-        }
-    });
+    var checked = PWM_MAIN.JSLibrary.arrayContains(resultValue,optionKey)
+    if (checked) {
+        PWM_MAIN.JSLibrary.removeFromArray(resultValue, optionKey);
+    } else {
+        resultValue.push(optionKey);
+    }
     PWM_CFGEDIT.writeSetting(keyName, resultValue);
 };
 
@@ -1524,7 +1320,7 @@ X509CertificateHandler.draw = function(keyName) {
 
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
 
-    var htmlBody = '<div style="max-height: 300px; overflow-y: auto">';
+    var htmlBody = '<div>';
     for (var certCounter in resultValue) {
         (function (counter) {
             var certificate = resultValue[counter];
@@ -1781,7 +1577,7 @@ PrivateKeyHandler.draw = function(keyName) {
 
     var resultValue = PWM_VAR['clientSettingCache'][keyName];
 
-    var htmlBody = '<div style="max-height: 300px; overflow-y: auto">';
+    var htmlBody = '<div>';
 
     var hasValue = resultValue !== undefined && 'key' in resultValue;
 

+ 33 - 35
webapp/src/main/webapp/public/resources/js/configeditor.js

@@ -156,44 +156,42 @@ PWM_CFGEDIT.handleWorkingIcon = function() {
 
 
 PWM_CFGEDIT.updateSettingDisplay = function(keyName, isDefault) {
-    require(["dojo"],function(dojo){
-        var resetImageButton = PWM_MAIN.getObject('resetButton-' + keyName);
-        var modifiedIcon = PWM_MAIN.getObject('modifiedNoticeIcon-' + keyName);
-        var settingSyntax = '';
-        try {
-            settingSyntax = PWM_SETTINGS['settings'][keyName]['syntax'];
-        } catch (e) { /* noop */ }  //setting keys may not be loaded
-
-        if (PWM_SETTINGS['settings'][keyName]) {
-            if (PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'],'NoDefault')) {
-                isDefault = true;
-            }
+    var resetImageButton = PWM_MAIN.getObject('resetButton-' + keyName);
+    var modifiedIcon = PWM_MAIN.getObject('modifiedNoticeIcon-' + keyName);
+    var settingSyntax = '';
+    try {
+        settingSyntax = PWM_SETTINGS['settings'][keyName]['syntax'];
+    } catch (e) { /* noop */ }  //setting keys may not be loaded
+
+    if (PWM_SETTINGS['settings'][keyName]) {
+        if (PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][keyName]['flags'],'NoDefault')) {
+            isDefault = true;
         }
+    }
 
-        if (!isDefault) {
-            if (resetImageButton) {
-                resetImageButton.style.visibility = 'visible';
-            }
-            if (modifiedIcon) {
-                modifiedIcon.style.display = 'inline';
-            }
-            try {
-                dojo.addClass('title_' + keyName,"modified");
-                dojo.addClass('titlePane_' + keyName,"modified");
-            } catch (e) { /* noop */ }
-        } else {
-            if (resetImageButton) {
-                resetImageButton.style.visibility = 'hidden';
-            }
-            if (modifiedIcon) {
-                modifiedIcon.style.display = 'none';
-            }
-            try {
-                dojo.removeClass('title_' + keyName,"modified");
-                dojo.removeClass('titlePane_' + keyName,"modified");
-            } catch (e) { /* noop */ }
+    if (!isDefault) {
+        if (resetImageButton) {
+            resetImageButton.style.visibility = 'visible';
         }
-    });
+        if (modifiedIcon) {
+            modifiedIcon.style.display = 'inline';
+        }
+        try {
+            document.getElementById('title_' + keyName).classList.add('modified');
+            document.getElementById('titlePane_' + keyName).classList.add('modified');
+        } catch (e) { /* noop */ }
+    } else {
+        if (resetImageButton) {
+            resetImageButton.style.visibility = 'hidden';
+        }
+        if (modifiedIcon) {
+            modifiedIcon.style.display = 'none';
+        }
+        try {
+            document.getElementById('title_' + keyName).classList.remove('modified');
+            document.getElementById('titlePane_' + keyName).classList.remove('modified');
+        } catch (e) { /* noop */ }
+    }
 };
 
 PWM_CFGEDIT.getSettingValueElement = function(settingKey) {

+ 3 - 3
webapp/src/main/webapp/public/resources/js/configmanager.js

@@ -452,7 +452,7 @@ PWM_CONFIG.initConfigManagerWordlistPage = function() {
 
 
 PWM_CONFIG.convertListOfIdentitiesToHtml = function(data) {
-    var html = '<div style="max-height: 500px; overflow-y: auto">';
+    var html = '<div class="panel-large overflow-y">';
     var users = data['users'];
     if (users && !PWM_MAIN.JSLibrary.isEmpty(users)) {
         html += '<table style="">';
@@ -470,9 +470,9 @@ PWM_CONFIG.convertListOfIdentitiesToHtml = function(data) {
     }
     html += '</div>';
 
-    html += '<br/><div class="noticebar" style="margin-right: 5px; margin-left: 5px">' + data['searchOperationSummary'];
+    html += '<br/><div class="footnote"><p>' + data['searchOperationSummary'] + '</p>';
     if (data['sizeExceeded']) {
-        html += ' ' + PWM_CONFIG.showString('Display_EditorLDAPSizeExceeded');
+        html += '<p>' + PWM_CONFIG.showString('Display_EditorLDAPSizeExceeded') + '</p>';
     }
     html += '</div>';
     return html;

+ 1 - 0
webapp/src/main/webapp/public/resources/style.css

@@ -435,6 +435,7 @@ input[type=password]::-ms-reveal {
     color: white;
     background: #3e374c;
     cursor: pointer;
+    white-space: nowrap;
 }
 
 .btn:disabled {

+ 27 - 10
webapp/src/main/webapp/public/resources/themes/pwm/configStyle.css

@@ -36,9 +36,7 @@ html {
     border-radius: 3px 3px 5px 5px;
     /* box-shadow: 2px 2px 1px 1px #dcdcdc; */
     margin-bottom: 10px;
-    animation: fadein 0.5s;
-    -moz-animation: fadein 0.5s; /* Firefox */
-    -webkit-animation: fadein 0.5s; /* Safari and Chrome */
+    animation: fadein 0.4s;
 }
 
 .setting_title {
@@ -72,6 +70,9 @@ html {
 
 .setting_body {
     padding: 5px;
+    max-height: 500px;
+    overflow-y: auto;
+    overflow-x: hidden;
 }
 
 .setting_body.modified {
@@ -268,7 +269,6 @@ table {
     position: absolute;
     top: 55px;
     width: 242px;
-    animation: fadein 0.5s;
 }
 
 #navigationTree {
@@ -295,20 +295,17 @@ table {
     padding-right: 6px;
     position: absolute;
     top: 55px;
-    width: 600px;
+    width: 606px;
 }
 
 #settingSearchPanel {
-    animation: fadein 1.5s;
+    animation: fadein 0.4s;
     background: none repeat scroll 0 0;
     left: 8px;
     position: absolute;
     top: 10px;
     width: 850px;
     z-index: 2;
-
-    -moz-animation: fadein 1.5s; /* Firefox */
-    -webkit-animation: fadein 1.5s; /* Safari and Chrome */
 }
 
 .setting_item_value_wrapper {
@@ -360,7 +357,7 @@ input:invalid{
     max-height: 250px;
     margin: 2px 2px 15px;
     overflow-x: auto;
-    animation: fadein 1s;
+    animation: fadein 0.4s;
 }
 
 /* This allows us to show text with carriage returns in it, without having to use <br> tags.  We may want to put this on .pane-help eventually */
@@ -475,6 +472,18 @@ input:invalid{
     max-height:50px;
 }
 
+.permissionProfileValueInput {
+    max-width: 100px;
+    width: 100px;
+    padding: 5px;
+    margin-left: 10px;
+}
+
+.permissionValuePanel {
+    width: 340px;
+    padding: 5px;
+}
+
 .editorIdleStatus {
     /*
     margin-left: auto;
@@ -598,3 +607,11 @@ select {
     max-height: 350px;
     overflow-y: auto
 }
+
+.panel-large {
+    max-height: 500px;
+}
+
+.overflow-y {
+    overflow-y: auto;
+}