浏览代码

server updates for peoplesearch advanced search

jrivard@gmail.com 6 年之前
父节点
当前提交
8455aaa07b

+ 9 - 2
server/src/main/java/password/pwm/config/PwmSetting.java

@@ -939,8 +939,8 @@ public enum PwmSetting
             "peopleSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
     PEOPLE_SEARCH_QUERY_MATCH(
             "peopleSearch.queryMatch", PwmSettingSyntax.USER_PERMISSION, PwmSettingCategory.PEOPLE_SEARCH ),
-    PEOPLE_SEARCH_SEARCH_ATTRIBUTES(
-            "peopleSearch.searchAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH ),
+    PEOPLE_SEARCH_SEARCH_FORM(
+            "peopleSearch.search.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH ),
     PEOPLE_SEARCH_RESULT_FORM(
             "peopleSearch.result.form", PwmSettingSyntax.FORM, PwmSettingCategory.PEOPLE_SEARCH ),
     PEOPLE_SEARCH_DETAIL_FORM(
@@ -969,6 +969,9 @@ public enum PwmSetting
             "peopleSearch.enableExport", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
     PEOPLE_SEARCH_IDLE_TIMEOUT_SECONDS(
             "peopleSearch.idleTimeout", PwmSettingSyntax.DURATION, PwmSettingCategory.PEOPLE_SEARCH ),
+    PEOPLE_SEARCH_ENABLE_ADVANCED_SEARCH(
+            "peopleSearch.advancedSearch.enable", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.PEOPLE_SEARCH ),
+
 
 
     // edirectory settings
@@ -1197,6 +1200,10 @@ public enum PwmSetting
 
     // deprecated.
 
+    // deprecated 2018-10-15
+    PEOPLE_SEARCH_SEARCH_ATTRIBUTES(
+            "peopleSearch.searchAttributes", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH ),
+
     // deprecated 2018-02-27
     RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME(
             "challenge.enforceMinimumPasswordLifetime", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.RECOVERY_OPTIONS ),

+ 27 - 0
server/src/main/java/password/pwm/config/stored/StoredConfigurationImpl.java

@@ -41,12 +41,14 @@ import password.pwm.config.PwmSettingTemplate;
 import password.pwm.config.PwmSettingTemplateSet;
 import password.pwm.config.StoredValue;
 import password.pwm.config.option.ADPolicyComplexity;
+import password.pwm.config.value.FormValue;
 import password.pwm.config.value.NamedSecretValue;
 import password.pwm.config.value.PasswordValue;
 import password.pwm.config.value.PrivateKeyValue;
 import password.pwm.config.value.StringArrayValue;
 import password.pwm.config.value.StringValue;
 import password.pwm.config.value.ValueFactory;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
@@ -1408,6 +1410,31 @@ public class StoredConfigurationImpl implements StoredConfiguration
                     storedConfiguration.resetSetting( PwmSetting.RECOVERY_ENFORCE_MINIMUM_PASSWORD_LIFETIME, profileID, actor );
                 }
             }
+
+            {
+                if ( !storedConfiguration.isDefaultValue( PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES ) )
+                {
+                    final List<String> oldValues = ( List<String> ) storedConfiguration.readSetting( PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES ).toNativeObject();
+
+                    final List<FormConfiguration> newValues = new ArrayList<>();
+                    for ( final String attribute : oldValues )
+                    {
+                        final FormConfiguration formConfiguration = FormConfiguration.builder()
+                                .name( attribute )
+                                .labels( Collections.singletonMap( "", attribute ) )
+                                .build();
+                        newValues.add( formConfiguration );
+                    }
+
+                    final ValueMetaData existingData = storedConfiguration.readSettingMetadata( PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES, null );
+                    final UserIdentity newActor = existingData != null && existingData.getUserIdentity() != null
+                            ? existingData.getUserIdentity()
+                            : actor;
+
+                    storedConfiguration.writeSetting( PwmSetting.PEOPLE_SEARCH_SEARCH_FORM, null, new FormValue( newValues ), newActor );
+                    storedConfiguration.resetSetting( PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES, null, actor );
+                }
+            }
         }
     }
 

+ 4 - 0
server/src/main/java/password/pwm/http/PwmSession.java

@@ -267,6 +267,10 @@ public class PwmSession implements Serializable
 
         if ( pwmRequest != null )
         {
+            final String nonceCookieName = pwmRequest.getConfig().readAppProperty( AppProperty.HTTP_COOKIE_NONCE_NAME );
+            pwmRequest.getPwmResponse().removeCookie( nonceCookieName, PwmHttpResponseWrapper.CookiePath.Application );
+            pwmRequest.setAttribute( PwmRequestAttribute.CookieNonce, null );
+
             try
             {
                 pwmRequest.getPwmApplication().getSessionStateService().clearLoginSession( pwmRequest );

+ 3 - 3
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskClientDataBean.java

@@ -148,9 +148,9 @@ public class HelpdeskClientDataBean implements Serializable
         }
         {
             final List<PeopleSearchClientConfigBean.SearchAttribute> searchAttributes = Arrays.asList(
-                    PeopleSearchClientConfigBean.SearchAttribute.builder().attribute( "Title" ).id( "title" ).build(),
-                    PeopleSearchClientConfigBean.SearchAttribute.builder().attribute( "Given Name" ).id( "givenName" ).build(),
-                    PeopleSearchClientConfigBean.SearchAttribute.builder().attribute( "First Name" ).id( "sn" ).build()
+                    PeopleSearchClientConfigBean.SearchAttribute.builder().type( FormConfiguration.Type.text ).label( "Title" ).attribute( "title" ).build(),
+                    PeopleSearchClientConfigBean.SearchAttribute.builder().type( FormConfiguration.Type.text ).label( "Given Name" ).attribute( "givenName" ).build(),
+                    PeopleSearchClientConfigBean.SearchAttribute.builder().type( FormConfiguration.Type.text ).label( "First Name" ).attribute( "sn" ).build()
             );
 
                     builder.enableAdvancedSearch( true );

+ 31 - 9
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchClientConfigBean.java

@@ -33,9 +33,11 @@ import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 
 import java.io.Serializable;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 @Value
@@ -58,8 +60,10 @@ public class PeopleSearchClientConfigBean implements Serializable
     @Builder
     public static class SearchAttribute implements Serializable
     {
-        private String id;
         private String attribute;
+        private String label;
+        private FormConfiguration.Type type;
+        private Map<String, String> options;
     }
 
 
@@ -72,19 +76,37 @@ public class PeopleSearchClientConfigBean implements Serializable
     {
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final Configuration configuration = pwmApplication.getConfig();
+        final Locale locale = pwmRequest.getLocale();
+
         final Map<String, String> searchColumns = new LinkedHashMap<>();
         final List<FormConfiguration> searchForm = configuration.readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
         for ( final FormConfiguration formConfiguration : searchForm )
         {
             searchColumns.put( formConfiguration.getName(),
-                    formConfiguration.getLabel( pwmRequest.getLocale() ) );
+                    formConfiguration.getLabel( locale ) );
+        }
+
+
+        final List<SearchAttribute> searchAttributes;
+        {
+            final List<SearchAttribute> returnList = new ArrayList<>( );
+            for ( final FormConfiguration formConfiguration : peopleSearchConfiguration.getSearchForm() )
+            {
+                final String attribute = formConfiguration.getName();
+                final String label = formConfiguration.getLabel( locale );
+
+                final SearchAttribute searchAttribute = SearchAttribute.builder()
+                        .attribute( attribute )
+                        .type( formConfiguration.getType() )
+                        .label( label )
+                        .options( formConfiguration.getSelectOptions() )
+                        .build();
+
+                returnList.add( searchAttribute );
+            }
+            searchAttributes = Collections.unmodifiableList( returnList );
         }
 
-        final List<SearchAttribute> searchAttributes = Arrays.asList(
-                SearchAttribute.builder().attribute( "Title" ).id( "title" ).build(),
-                SearchAttribute.builder().attribute( "Given Name" ).id( "givenName" ).build(),
-                SearchAttribute.builder().attribute( "First Name" ).id( "sn" ).build()
-        );
 
         return PeopleSearchClientConfigBean.builder()
                 .searchColumns( searchColumns )
@@ -93,7 +115,7 @@ public class PeopleSearchClientConfigBean implements Serializable
                 .orgChartShowChildCount( peopleSearchConfiguration.isOrgChartShowChildCount() )
                 .orgChartMaxParents( peopleSearchConfiguration.getOrgChartMaxParents() )
 
-                .enableAdvancedSearch( true )
+                .enableAdvancedSearch( peopleSearchConfiguration.isEnableAdvancedSearch() )
                 .maxAdvancedSearchAttributes( 3 )
                 .advancedSearchAttributes( searchAttributes )
 

+ 30 - 0
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchConfiguration.java

@@ -29,13 +29,17 @@ import password.pwm.bean.UserIdentity;
 import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.profile.LdapProfile;
+import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.config.value.data.UserPermission;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmRequest;
 import password.pwm.ldap.LdapPermissionTester;
 import password.pwm.util.java.TimeDuration;
 
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 public class PeopleSearchConfiguration
 {
@@ -139,6 +143,28 @@ public class PeopleSearchConfiguration
         return Integer.parseInt( pwmRequest.getConfig().readAppProperty( AppProperty.PEOPLESEARCH_EXPORT_CSV_MAX_ITEMS ) );
     }
 
+    List<FormConfiguration> getSearchForm()
+    {
+        return pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_SEARCH_FORM );
+    }
+
+    Set<String> getSearchAttributes()
+    {
+        final List<FormConfiguration> searchForm = getSearchForm();
+
+        return Collections.unmodifiableSet( new LinkedHashSet<>( FormConfiguration.convertToListOfNames( searchForm ) ) );
+    }
+
+    List<FormConfiguration> getResultForm()
+    {
+        return pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
+    }
+
+    int getResultLimit()
+    {
+        return ( int ) pwmRequest.getConfig().readSettingAsLong( PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT );
+    }
+
     public static PeopleSearchConfiguration forRequest(
             final PwmRequest pwmRequest
     )
@@ -146,4 +172,8 @@ public class PeopleSearchConfiguration
         return new PeopleSearchConfiguration( pwmRequest );
     }
 
+    public boolean isEnableAdvancedSearch()
+    {
+        return pwmApplication.getConfig().readSettingAsBoolean( PwmSetting.PEOPLE_SEARCH_ENABLE_ADVANCED_SEARCH );
+    }
 }

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

@@ -33,7 +33,6 @@ import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.UserIdentity;
-import password.pwm.config.Configuration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.value.data.FormConfiguration;
 import password.pwm.error.ErrorInformation;
@@ -69,11 +68,11 @@ import java.io.Serializable;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -104,12 +103,11 @@ class PeopleSearchDataReader
     }
 
     SearchResultBean makeSearchResultBean(
-            final String searchData,
-            final boolean includeDisplayName
+            final SearchRequestBean searchRequestBean
     )
             throws PwmUnrecoverableException
     {
-        final CacheKey cacheKey = makeCacheKey( SearchResultBean.class.getSimpleName(), searchData + "|" + includeDisplayName );
+        final CacheKey cacheKey = makeCacheKey( SearchResultBean.class.getSimpleName(), JsonUtil.serialize( searchRequestBean ) );
 
         {
             // try to serve from cache first
@@ -127,12 +125,14 @@ class PeopleSearchDataReader
         }
 
         // if not in cache, build results from ldap
-        final SearchResultBean searchResultBean = makeSearchResultsImpl( pwmRequest, searchData, includeDisplayName )
+        final SearchResultBean searchResultBean = makeSearchResultsImpl( searchRequestBean )
                 .toBuilder().fromCache( false ).build();
 
         StatisticsManager.incrementStat( pwmRequest, Statistic.PEOPLESEARCH_SEARCHES );
         storeDataInCache( pwmRequest.getPwmApplication(), cacheKey, searchResultBean );
-        LOGGER.trace( pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + searchData + "'" );
+        LOGGER.trace( pwmRequest, "returning " + searchResultBean.getSearchResults().size()
+                + " results for search request "
+                + JsonUtil.serialize( searchRequestBean ) );
         return searchResultBean;
     }
 
@@ -369,12 +369,6 @@ class PeopleSearchDataReader
                 keyString );
     }
 
-    private static Set<String> getSearchAttributes( final Configuration configuration )
-    {
-        final List<String> searchResultForm = configuration.readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_SEARCH_ATTRIBUTES );
-        return Collections.unmodifiableSet( new HashSet<>( searchResultForm ) );
-    }
-
     private OrgChartReferenceBean makeOrgChartReferenceForIdentity(
             final UserIdentity userIdentity
     )
@@ -553,7 +547,7 @@ class PeopleSearchDataReader
     )
             throws PwmUnrecoverableException
     {
-        final Set<String> searchAttributes = getSearchAttributes( pwmRequest.getConfig() );
+        final Set<String> searchAttributes = peopleSearchConfiguration.getSearchAttributes();
         final Map<String, AttributeDetailBean> returnObj = new LinkedHashMap<>();
         for ( final FormConfiguration formConfiguration : detailForm )
         {
@@ -648,7 +642,7 @@ class PeopleSearchDataReader
         final Instant startTime = Instant.now();
         final CacheLoader<Boolean> cacheLoader = () ->
         {
-            final String filterSetting = getSearchFilter( pwmRequest.getConfig() );
+            final String filterSetting = makeSimpleSearchFilter( );
             String filterString = filterSetting.replace( PwmConstants.VALUE_REPLACEMENT_USERNAME, "*" );
             while ( filterString.contains( "**" ) )
             {
@@ -674,16 +668,16 @@ class PeopleSearchDataReader
         }
     }
 
-    private static String getSearchFilter( final Configuration configuration )
+    private String makeSimpleSearchFilter()
     {
-        final String configuredFilter = configuration.readSettingAsString( PwmSetting.PEOPLE_SEARCH_SEARCH_FILTER );
+        final String configuredFilter = pwmRequest.getConfig().readSettingAsString( PwmSetting.PEOPLE_SEARCH_SEARCH_FILTER );
         if ( configuredFilter != null && !configuredFilter.isEmpty() )
         {
             return configuredFilter;
         }
 
-        final List<String> defaultObjectClasses = configuration.readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES );
-        final Set<String> searchAttributes = getSearchAttributes( configuration );
+        final List<String> defaultObjectClasses = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES );
+        final Set<String> searchAttributes = peopleSearchConfiguration.getSearchAttributes();
         final StringBuilder filter = new StringBuilder();
 
         //open AND clause for objectclasses and attributes
@@ -709,6 +703,38 @@ class PeopleSearchDataReader
         return filter.toString();
     }
 
+    private String makeAdvancedFilter( final Set<String> attributesInSearchRequest )
+    {
+        final List<String> defaultObjectClasses = pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.DEFAULT_OBJECT_CLASSES );
+        final Set<String> searchAttributes = peopleSearchConfiguration.getSearchAttributes();
+        final StringBuilder filter = new StringBuilder();
+
+        //open AND clause for objectclasses and attributes
+        filter.append( "(&" );
+        for ( final String objectClass : defaultObjectClasses )
+        {
+            filter.append( "(objectClass=" ).append( objectClass ).append( ")" );
+        }
+
+        // open AND clause for attributes
+        filter.append( "(&" );
+
+        for ( final String searchAttribute : searchAttributes )
+        {
+            if ( attributesInSearchRequest.contains( searchAttribute ) )
+            {
+                filter.append( "(" ).append( searchAttribute ).append( "=*%" ).append( searchAttribute ).append( "%*)" );
+            }
+        }
+
+        // close attribute clause
+        filter.append( ")" );
+
+        // close AND clause
+        filter.append( ")" );
+        return filter.toString();
+    }
+
     private boolean useProxy( )
     {
 
@@ -772,48 +798,85 @@ class PeopleSearchDataReader
     }
 
     private SearchResultBean makeSearchResultsImpl(
-            final PwmRequest pwmRequest,
-            final String username,
-            final boolean includeDisplayName
+            final SearchRequestBean searchRequest
     )
             throws PwmUnrecoverableException
     {
-        final Instant startTime = Instant.now();
+        Objects.requireNonNull( searchRequest );
 
-        if ( username == null || username.length() < 1 )
-        {
-            return SearchResultBean.builder().searchResults( Collections.emptyList() ).build();
-        }
+        final Instant startTime = Instant.now();
 
-        final boolean useProxy = useProxy();
-        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
+        final SearchRequestBean.SearchMode searchMode = searchRequest.getMode() == null
+                ? SearchRequestBean.SearchMode.simple
+                : searchRequest.getMode();
 
         final SearchConfiguration searchConfiguration;
         {
             final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder();
             builder.contexts( pwmRequest.getConfig().readSettingAsStringArray( PwmSetting.PEOPLE_SEARCH_SEARCH_BASE ) );
             builder.enableContextValidation( false );
-            builder.username( username );
             builder.enableValueEscaping( false );
-            builder.filter( getSearchFilter( pwmRequest.getConfig() ) );
             builder.enableSplitWhitespace( true );
 
-            if ( !useProxy )
+            if ( !useProxy() )
             {
                 builder.ldapProfile( pwmRequest.getPwmSession().getUserInfo().getUserIdentity().getLdapProfileID() );
                 builder.chaiProvider( pwmRequest.getPwmSession().getSessionManager().getChaiProvider() );
             }
+
+            switch ( searchMode )
+            {
+                case simple:
+                {
+                    if ( StringUtil.isEmpty( searchRequest.getUsername() ) )
+                    {
+                        return SearchResultBean.builder().searchResults( Collections.emptyList() ).build();
+                    }
+
+                    builder.filter( makeSimpleSearchFilter() );
+                    builder.username( searchRequest.getUsername() );
+                }
+                break;
+
+                case advanced:
+                {
+                    if ( JavaHelper.isEmpty( searchRequest.getSearchValues() ) )
+                    {
+                        return SearchResultBean.builder().searchResults( Collections.emptyList() ).build();
+                    }
+
+                    final Map<FormConfiguration, String> formValues = new LinkedHashMap<>();
+                    final Map<String, String> requestSearchValues = SearchRequestBean.searchValueToMap( searchRequest.getSearchValues() );
+                    for ( final FormConfiguration formConfiguration : peopleSearchConfiguration.getSearchForm() )
+                    {
+                        final String attribute = formConfiguration.getName();
+                        final String value = requestSearchValues.get( attribute );
+                        if ( !StringUtil.isEmpty( value ) )
+                        {
+                            formValues.put( formConfiguration, value );
+                        }
+                    }
+
+                    builder.filter( makeAdvancedFilter( requestSearchValues.keySet() ) );
+                    builder.formValues( formValues );
+                }
+                break;
+
+                default:
+                    JavaHelper.unhandledSwitchStatement( searchMode );
+            }
+
             searchConfiguration = builder.build();
         }
 
+        final UserSearchEngine userSearchEngine = pwmRequest.getPwmApplication().getUserSearchEngine();
+
         final UserSearchResults results;
         final boolean sizeExceeded;
         try
         {
-            final List<FormConfiguration> searchForm = pwmRequest.getConfig().readSettingAsForm(
-                    PwmSetting.PEOPLE_SEARCH_RESULT_FORM );
-            final int maxResults = ( int ) pwmRequest.getConfig().readSettingAsLong(
-                    PwmSetting.PEOPLE_SEARCH_RESULT_LIMIT );
+            final List<FormConfiguration> searchForm = peopleSearchConfiguration.getResultForm();
+            final int maxResults = peopleSearchConfiguration.getResultLimit();
             final Locale locale = pwmRequest.getLocale();
             results = userSearchEngine.performMultiUserSearchFromForm( locale, searchConfiguration, maxResults, searchForm, pwmRequest.getSessionLabel() );
             sizeExceeded = results.isSizeExceeded();
@@ -826,7 +889,7 @@ class PeopleSearchDataReader
         }
 
         final List<Map<String, Object>> resultOutput = new ArrayList<>( results.resultsAsJsonOutput( pwmRequest.getPwmApplication(), null ) );
-        if ( includeDisplayName )
+        if ( searchRequest.isIncludeDisplayName() )
         {
             for ( final Map<String, Object> map : resultOutput )
             {

+ 5 - 11
server/src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -55,7 +55,6 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Map;
 
 public abstract class PeopleSearchServlet extends ControlledPwmServlet
 {
@@ -120,7 +119,7 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
             final PwmRequest pwmRequest
 
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+            throws PwmUnrecoverableException, IOException
     {
         final PeopleSearchConfiguration peopleSearchConfiguration = PeopleSearchConfiguration.forRequest( pwmRequest );
 
@@ -140,24 +139,19 @@ public abstract class PeopleSearchServlet extends ControlledPwmServlet
     private ProcessStatus restSearchRequest(
             final PwmRequest pwmRequest
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
+            throws PwmUnrecoverableException, IOException
     {
-        final Map<String, Object> jsonBodyMap = pwmRequest.readBodyAsJsonMap( PwmHttpRequestWrapper.Flag.BypassValidation );
-        final String username = jsonBodyMap.get( "username" ) == null
-                ? null
-                : jsonBodyMap.get( "username" ).toString();
-
-        final boolean includeDisplayName = pwmRequest.readParameterAsBoolean( "includeDisplayName" );
+        final SearchRequestBean searchRequest = JsonUtil.deserialize( pwmRequest.readRequestBodyAsString(), SearchRequestBean.class );
 
         final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader( pwmRequest );
 
-        final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean( username, includeDisplayName );
+        final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean( searchRequest );
         final RestResultBean restResultBean = RestResultBean.withData( searchResultBean );
 
         addExpiresHeadersToResponse( pwmRequest );
         pwmRequest.outputJsonResult( restResultBean );
 
-        LOGGER.trace( pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + username + "'" );
+        LOGGER.trace( pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request " + JsonUtil.serialize( searchRequest ) );
         return ProcessStatus.Halt;
     }
 

+ 67 - 0
server/src/main/java/password/pwm/http/servlet/peoplesearch/SearchRequestBean.java

@@ -0,0 +1,67 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+package password.pwm.http.servlet.peoplesearch;
+
+import lombok.Builder;
+import lombok.Value;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Value
+@Builder
+public class SearchRequestBean implements Serializable
+{
+    @Builder.Default
+    private SearchMode mode = SearchMode.simple;
+
+    private String username;
+    private List<SearchValue> searchValues;
+    private boolean includeDisplayName;
+
+    public enum SearchMode
+    {
+        simple,
+        advanced,
+    }
+
+    @Value
+    public static class SearchValue implements Serializable
+    {
+        private String key;
+        private String value;
+    }
+
+    static Map<String, String> searchValueToMap( final List<SearchValue> input )
+    {
+        final Map<String, String> returnMap = new LinkedHashMap<>();
+        for ( final SearchValue searchValue : input )
+        {
+            returnMap.put( searchValue.getKey(), searchValue.getValue() );
+        }
+        return Collections.unmodifiableMap( returnMap );
+    }
+}

+ 20 - 1
server/src/main/resources/password/pwm/config/PwmSetting.xml

@@ -3141,7 +3141,7 @@
             <value>{"type":"ldapQuery","ldapProfileID":"all","ldapQuery":"(objectClass=*)"}</value>
         </default>
     </setting>
-    <setting hidden="false" key="peopleSearch.searchAttributes" level="1" required="false">
+    <setting hidden="true" key="peopleSearch.searchAttributes" level="1" required="false">
         <ldapPermission actor="self_other" access="read"/>
         <default>
             <value>givenName</value>
@@ -3150,6 +3150,20 @@
             <value>mail</value>
         </default>
     </setting>
+    <setting hidden="false" key="peopleSearch.search.form" level="1" required="true">
+        <flag>Form_HideStandardOptions</flag>
+        <ldapPermission actor="self_other" access="read"/>
+        <default>
+            <value>{"name":"givenName","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"First Name"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{}}</value>
+            <value>{"name":"sn","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"Last Name"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{}}</value>
+            <value>{"name":"title","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"Title"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{}}</value>
+            <value>{"name":"mail","minimumLength":1,"maximumLength":64,"type":"text","required":true,"confirmationRequired":false,"readonly":false,"labels":{"":"Email"},"regexErrors":{"":""},"description":{"":""},"selectOptions":{}}</value>
+        </default>
+        <options>
+            <option value="text">text</option>
+            <option value="select">select</option>
+        </options>
+    </setting>
     <setting hidden="false" key="peopleSearch.searchFilter" level="2" required="false">
         <default/>
     </setting>
@@ -3259,6 +3273,11 @@
             <value>0</value>
         </default>
     </setting>
+    <setting hidden="false" key="peopleSearch.advancedSearch.enable" level="1">
+        <default>
+            <value>false</value>
+        </default>
+    </setting>
     <setting hidden="false" key="peopleSearch.orgChart.parentAttribute" level="1">
         <ldapPermission actor="self_other" access="read"/>
         <default>

+ 4 - 0
server/src/main/resources/password/pwm/i18n/PwmSetting.properties

@@ -574,6 +574,7 @@ Setting_Description_passwordSync.enableReplicaCheck=Enable this option to check
 Setting_Description_passwordSyncMaxWaitTime=Specify how long, during a password change, the system waits for the password to be synchronized to all configured LDAP servers.  In cases where the synchronization might take an extraordinary amount of time, this setting prevents the page from timing out.<br/><br/>Specify the value in seconds.
 Setting_Description_passwordSyncMinWaitTime=Specify how long, during a password change, the system waits before forwarding the user.  This gives any background synchronization processes time to execute before the user executes the next operation.<br/><br/>Specify the value in seconds.
 Setting_Description_password.wordlist.wordSize=Specify the minimum number of characters in the password that @PwmAppName@ checks against the Word List dictionary. For example, if the password the system checks is "wordlist" and this setting is set to 6, then the combinations "wordli", "wordlis", "wordlist", "ordlis", "ordlist", and "rdlist" are all checked against the configured dictionary. If any of these values are equal to any word in the Word List dictionary, then the system considers the password to match the Word List and rejects it. If this value is set to zero or the password to check is smaller than the value specified here, then the system checks the entire password against the Word List but not any smaller parts of it.
+Setting_Description_peopleSearch.advancedSearch.enable=Enable advanced searching user interface.  Allows users to specify individual attributes for searching.
 Setting_Description_peopleSearch.detail.form=Specify attributes to show in the detail view of an individual person's record.
 Setting_Description_peopleSearch.displayName.cardLabels=Specify the display labels for the user panel in the People Search detail and on the organizational chart views.  You can use LDAP attribute value such as <code>@LDAP\:givenName@</code> macros.
 Setting_Description_peopleSearch.displayName.user=Specify the display name for userDN type records.  Use macros to control the presentation such as the LDAP attribute macro <code>@LDAP\:givenName@</code>.
@@ -594,6 +595,7 @@ Setting_Description_peopleSearch.queryMatch=Define an LDAP directory filter that
 Setting_Description_peopleSearch.result.form=Specify the attributes the People Search module shows in the search results table during searches.
 Setting_Description_peopleSearch.result.limit=Specify the maximum number of records @PwmAppName@ returns while searching.
 Setting_Description_peopleSearch.searchAttributes=Add a list of LDAP attributes to search when generating an automatic search filter for the setting <a data-gotoSettingLink\="peopleSearch.searchFilter">@PwmSettingReference\:peopleSearch.searchFilter@</a>.  @PwmAppName@ also uses it to determine which fields in the user detail form it shows in the "Like" search option.
+Setting_Description_peopleSearch.search.form=Add a list of LDAP attributes to search when generating an automatic search filter for the setting <a data-gotoSettingLink\="peopleSearch.searchFilter">@PwmSettingReference\:peopleSearch.searchFilter@</a>.  @PwmAppName@ also uses it to determine which fields in the user detail form it shows in the "Like" search option.
 Setting_Description_peopleSearch.searchBase=Specify the LDAP search bases for the People Search module. If empty, @PwmAppName@ uses the default LDAP search bases.
 Setting_Description_peopleSearch.searchFilter=Specify the LDAP search filter the People Search module uses to query the directory.  Substitute <i>%USERNAME%</i> for user-supplied user names. If blank, @PwmAppName@ auto-generates the search filter based on the values in the setting <a data-gotoSettingLink\="peopleSearch.searchAttributes">@PwmSettingReference\:peopleSearch.searchAttributes@</a>.\n        <br/><br>\n        Example\: <code>(&(objectClass\=Person)(|(givenName\=*%USERNAME%*)(sn\=*%USERNAME%*)(mail\=*%USERNAME%*)(telephoneNumber\=*%USERNAME%*)))</code>\n\n
 Setting_Description_peopleSearch.useProxy=Enable this option to use the LDAP proxy account to perform searches. For proper security in most environments, do <b>not</b> enable this setting.
@@ -1078,6 +1080,7 @@ Setting_Label_passwordSync.enableReplicaCheck=Password Sync Enable Replication C
 Setting_Label_passwordSyncMaxWaitTime=Password Change Maximum Wait Time
 Setting_Label_passwordSyncMinWaitTime=Password Change Minimum Wait Time
 Setting_Label_password.wordlist.wordSize=Word List Word Size Check
+Setting_Label_peopleSearch.advancedSearch.enable=Enable Advanced Search
 Setting_Label_peopleSearch.detail.form=Search Detail Attributes
 Setting_Label_peopleSearch.displayName.cardLabels=Person Detail Display Labels
 Setting_Label_peopleSearch.displayName.user=UserDN Name Display
@@ -1098,6 +1101,7 @@ Setting_Label_peopleSearch.queryMatch=Permitted Users
 Setting_Label_peopleSearch.result.form=Search Result Attributes
 Setting_Label_peopleSearch.result.limit=Search Result Limit
 Setting_Label_peopleSearch.searchAttributes=Search Attributes
+Setting_Label_peopleSearch.search.form=Search Attributes
 Setting_Label_peopleSearch.searchBase=LDAP Search base
 Setting_Label_peopleSearch.searchFilter=People Search LDAP Filter
 Setting_Label_peopleSearch.useProxy=Use Proxy Account

+ 7 - 4
webapp/src/main/webapp/public/resources/js/configeditor-settings-form.js

@@ -225,6 +225,8 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) {
     var type = PWM_VAR['clientSettingCache'][keyName][iteration]['type'];
     var settings = PWM_SETTINGS['settings'][keyName];
     var currentValue = PWM_VAR['clientSettingCache'][keyName][iteration];
+    var options = PWM_SETTINGS['settings'][keyName]['options'];
+
 
     var hideStandardOptions = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_HideStandardOptions') || type === 'photo';
     var showRequired = PWM_MAIN.JSLibrary.arrayContains(settings['flags'],'Form_ShowRequiredOption');
@@ -286,10 +288,11 @@ FormTableHandler.showOptionsDialog = function(keyName, iteration) {
         bodyText += '</tr><tr>';
         bodyText += '<td id="' + inputID + '-label-js" class="key" title="' + PWM_CONFIG.showString('Tooltip_FormOptions_Javascript') + '">JavaScript (Depreciated)</td><td><input type="text" class="configStringInput" style="width:300px" id="' + inputID + 'javascript' + '"/></td>';
         bodyText += '</tr><tr>';
-        if (currentValue['type'] === 'select') {
-            bodyText += '<td class="key">Select Options</td><td><button class="btn" id="' + inputID + 'editOptionsButton"><span class="btn-icon pwm-icon pwm-icon-list-ul"/> Edit</button></td>';
-            bodyText += '</tr>';
-        }
+    }
+
+    if ('select' in options) {
+        bodyText += '<td class="key">Select Options</td><td><button class="btn" id="' + inputID + 'editOptionsButton"><span class="btn-icon pwm-icon pwm-icon-list-ul"/> Edit</button></td>';
+        bodyText += '</tr>';
     }
 
     if (showSource) {