Browse Source

peoplesearch orgchart improvements

jrivard 10 years ago
parent
commit
7b67234fc4
29 changed files with 475 additions and 259 deletions
  1. 7 3
      pwm/servlet/src/password/pwm/PwmApplication.java
  2. 11 2
      pwm/servlet/src/password/pwm/bean/UserIdentity.java
  3. 1 1
      pwm/servlet/src/password/pwm/config/FormUtility.java
  4. 2 0
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  5. 13 1
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  6. 7 1
      pwm/servlet/src/password/pwm/http/PwmRequest.java
  7. 14 9
      pwm/servlet/src/password/pwm/http/SessionManager.java
  8. 56 1
      pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java
  9. 13 12
      pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java
  10. 0 4
      pwm/servlet/src/password/pwm/http/servlet/CommandServlet.java
  11. 7 10
      pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java
  12. 8 6
      pwm/servlet/src/password/pwm/http/servlet/GuestRegistrationServlet.java
  13. 7 6
      pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java
  14. 7 6
      pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java
  15. 181 76
      pwm/servlet/src/password/pwm/http/servlet/PeopleSearchServlet.java
  16. 0 1
      pwm/servlet/src/password/pwm/http/servlet/ResourceFileServlet.java
  17. 7 6
      pwm/servlet/src/password/pwm/http/servlet/UpdateProfileServlet.java
  18. 1 1
      pwm/servlet/src/password/pwm/i18n/Config.properties
  19. 2 0
      pwm/servlet/src/password/pwm/i18n/Display.java
  20. 1 0
      pwm/servlet/src/password/pwm/i18n/Display.properties
  21. 0 5
      pwm/servlet/src/password/pwm/ldap/LdapUserDataReader.java
  22. 1 1
      pwm/servlet/src/password/pwm/util/cache/CachePolicy.java
  23. 3 0
      pwm/servlet/src/password/pwm/util/localdb/LocalDBUtility.java
  24. 38 30
      pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java
  25. 18 20
      pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java
  26. 1 1
      pwm/servlet/web/public/resources/configStyle.css
  27. 16 11
      pwm/servlet/web/public/resources/js/configeditor-settings.js
  28. 46 43
      pwm/servlet/web/public/resources/js/peoplesearch.js
  29. 7 2
      pwm/servlet/web/public/resources/style.css

+ 7 - 3
pwm/servlet/src/password/pwm/PwmApplication.java

@@ -321,10 +321,14 @@ public class PwmApplication {
     }
 
     public ChaiUser getProxiedChaiUser(final UserIdentity userIdentity)
-            throws ChaiUnavailableException, PwmUnrecoverableException
+            throws PwmUnrecoverableException
     {
-        final ChaiProvider proxiedProvider = getProxyChaiProvider(userIdentity.getLdapProfileID());
-        return ChaiFactory.createChaiUser(userIdentity.getUserDN(), proxiedProvider);
+        try {
+            final ChaiProvider proxiedProvider = getProxyChaiProvider(userIdentity.getLdapProfileID());
+            return ChaiFactory.createChaiUser(userIdentity.getUserDN(), proxiedProvider);
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(PwmError.forChaiError(e.getErrorCode()));
+        }
     }
 
     public ChaiProvider getProxyChaiProvider(final String identifier)

+ 11 - 2
pwm/servlet/src/password/pwm/bean/UserIdentity.java

@@ -38,6 +38,8 @@ public class UserIdentity implements Serializable, Comparable {
     private static final String CRYPO_HEADER = "ui_C-";
     private static final String DELIM_SEPARATOR = "|";
 
+    private transient String obfuscatedValue;
+
     private String userDN;
     private String ldapProfile;
 
@@ -73,11 +75,18 @@ public class UserIdentity implements Serializable, Comparable {
     }
 
     public String toObfuscatedKey(final Configuration configuration)
-            throws PwmUnrecoverableException {
+            throws PwmUnrecoverableException
+    {
+        final String cachedValue = obfuscatedValue;
+        if (cachedValue != null) {
+            return cachedValue;
+        }
         try {
             final SecretKey secretKey = configuration.getSecurityKey();
             final String jsonValue = JsonUtil.serialize(this);
-            return CRYPO_HEADER + SecureHelper.encryptToString(jsonValue, secretKey, true);
+            final String localValue = CRYPO_HEADER + SecureHelper.encryptToString(jsonValue, secretKey, true);
+            this.obfuscatedValue = localValue;
+            return localValue;
         } catch (Exception e) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unexpected error making obfuscated user key: " + e.getMessage()));
         }

+ 1 - 1
pwm/servlet/src/password/pwm/config/FormUtility.java

@@ -185,7 +185,7 @@ public class FormUtility {
         searchConfiguration.setFilter(filter.toString());
 
         int resultSearchSizeLimit = 1 + (excludeDN == null ? 0 : excludeDN.size());
-        final CachePolicy cachePolicy = CachePolicy.makePolicy(30 * 1000);
+        final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(30 * 1000);
 
         try {
             final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmApplication, SessionLabel.SYSTEM_LABEL);

+ 2 - 0
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -814,6 +814,8 @@ public enum PwmSetting {
             "peopleSearch.orgChart.parentAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH),
     PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE(
             "peopleSearch.orgChart.childAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.PEOPLE_SEARCH),
+    PEOPLE_SEARCH_ORGCHART_DISPLAY_VALUES(
+            "peopleSearch.orgChart.displayValues", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.PEOPLE_SEARCH),
 
 
 

+ 13 - 1
pwm/servlet/src/password/pwm/config/PwmSetting.xml

@@ -3024,7 +3024,7 @@
         <label>PeopleSearch Maximum Cache Seconds</label>
         <description><![CDATA[]]></description>
         <default>
-            <value>3600</value>
+            <value>120</value>
         </default>
     </setting>
     <setting key="peopleSearch.photo.queryFilter" level="2">
@@ -3055,6 +3055,18 @@
             <value>directReports</value>
         </default>
     </setting>
+    <setting key="peopleSearch.orgChart.displayValues" level="1">
+        <label>Org Chart Display Labels</label>
+        <description/>
+        <default>
+            <value>@LDAP:givenName@ @LDAP:sn@</value>
+            <value>@LDAP:title@</value>
+            <value>@LDAP:mail@</value>
+        </default>
+        <options>
+            <option value="max">3</option>
+        </options>
+    </setting>
     <setting key="ldap.edirectory.enableNmas" level="1" required="true">
         <label>Enable NMAS Extensions</label>
         <description><![CDATA[When connecting to a NetIQ eDirectory LDAP directory, this parameter will control if NMAS extensions will be used when connecting to the ldap directory.  Enabling nmas results in:<ul><li>better error messages when using universal password policies</li><li>better error handling during certain change password scenarios</li></ul>Unless you are using an older version of eDirectory (pre 8.8 or before), it is generally best to set this to true.<br/><br/>All NMAS operations require an SSL connection to the directory.]]></description>

+ 7 - 1
pwm/servlet/src/password/pwm/http/PwmRequest.java

@@ -375,7 +375,13 @@ public class PwmRequest extends PwmHttpRequestWrapper implements Serializable {
 
     public void markPreLoginUrl()
     {
-        final String originalRequestedUrl = this.getHttpServletRequest().getRequestURL().toString();
+        String originalRequestedUrl = this.getHttpServletRequest().getRequestURL().toString();
+        {
+            final String queryString = this.getHttpServletRequest().getQueryString();
+            if (queryString != null && queryString.length() > 0) {
+                originalRequestedUrl += "?" + queryString;
+            }
+        }
         if (pwmSession.getSessionStateBean().getOriginalRequestURL() == null) {
             LOGGER.trace(this, "noted originally requested url as: " + originalRequestedUrl);
             pwmSession.getSessionStateBean().setOriginalRequestURL(originalRequestedUrl);

+ 14 - 9
pwm/servlet/src/password/pwm/http/SessionManager.java

@@ -159,16 +159,21 @@ public class SessionManager implements Serializable {
     }
 
     public ChaiUser getActor(final PwmApplication pwmApplication, final UserIdentity userIdentity)
-            throws PwmUnrecoverableException, ChaiUnavailableException {
-        if (!pwmSession.getSessionStateBean().isAuthenticated()) {
-            throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED);
-        }
-        final UserIdentity thisIdentity = pwmSession.getUserInfoBean().getUserIdentity();
-        if (thisIdentity.getLdapProfileID() == null || userIdentity.getLdapProfileID() == null) {
-            throw new PwmUnrecoverableException(PwmError.ERROR_NO_LDAP_CONNECTION);
+            throws PwmUnrecoverableException
+    {
+        try {
+            if (!pwmSession.getSessionStateBean().isAuthenticated()) {
+                throw new PwmUnrecoverableException(PwmError.ERROR_AUTHENTICATION_REQUIRED);
+            }
+            final UserIdentity thisIdentity = pwmSession.getUserInfoBean().getUserIdentity();
+            if (thisIdentity.getLdapProfileID() == null || userIdentity.getLdapProfileID() == null) {
+                throw new PwmUnrecoverableException(PwmError.ERROR_NO_LDAP_CONNECTION);
+            }
+            final ChaiProvider provider = this.getChaiProvider();
+            return ChaiFactory.createChaiUser(userIdentity.getUserDN(), provider);
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(PwmError.forChaiError(e.getErrorCode()));
         }
-        final ChaiProvider provider = this.getChaiProvider();
-        return ChaiFactory.createChaiUser(userIdentity.getUserDN(), provider);
     }
 
     public UserDataReader getUserDataReader(final PwmApplication pwmApplication)

+ 56 - 1
pwm/servlet/src/password/pwm/http/client/PwmHttpClient.java

@@ -7,9 +7,15 @@ import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.*;
+import org.apache.http.conn.ClientConnectionManager;
 import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.SingleClientConnManager;
 import org.apache.http.params.HttpProtocolParams;
 import org.apache.http.util.EntityUtils;
 import password.pwm.PwmApplication;
@@ -23,9 +29,17 @@ import password.pwm.http.PwmSession;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.logging.PwmLogger;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
 import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -44,7 +58,14 @@ public class PwmHttpClient {
     }
 
     public static HttpClient getHttpClient(final Configuration configuration) {
-        final DefaultHttpClient httpClient = new DefaultHttpClient();
+        DefaultHttpClient httpClient;
+        try {
+            ClientConnectionManager clientConnectionManager = ccm();
+            httpClient = new DefaultHttpClient(ccm());
+        } catch (Exception e) {
+            e.printStackTrace();
+            httpClient = new DefaultHttpClient();
+        }
         final String strValue = configuration.readSettingAsString(PwmSetting.HTTP_PROXY_URL);
         if (strValue != null && strValue.length() > 0) {
             final URI proxyURI = URI.create(strValue);
@@ -160,5 +181,39 @@ public class PwmHttpClient {
         LOGGER.trace(pwmSession, "received response (id=" + counter + ") in " + duration.asCompactString() + ": " + httpClientResponse.toDebugString());
         return httpClientResponse;
     }
+
+    private static ClientConnectionManager ccm()
+            throws NoSuchAlgorithmException, KeyManagementException
+    {
+        SSLContext sslContext = SSLContext.getInstance("SSL");
+
+        // set up a TrustManager that trusts everything
+        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
+            public X509Certificate[] getAcceptedIssuers() {
+                System.out.println("getAcceptedIssuers =============");
+                return new X509Certificate[0];
+            }
+
+            public void checkClientTrusted(X509Certificate[] certs,
+                                           String authType) {
+                System.out.println("checkClientTrusted =============");
+            }
+
+            public void checkServerTrusted(X509Certificate[] certs,
+                                           String authType) {
+                System.out.println("checkServerTrusted =============");
+            }
+        }}, new SecureRandom());
+
+        SSLSocketFactory sf = new SSLSocketFactory(sslContext);
+        HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
+
+        sf.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);        Scheme httpsScheme = new Scheme("https", 443, sf);
+        SchemeRegistry schemeRegistry = new SchemeRegistry();
+        schemeRegistry.register(httpsScheme);
+
+
+        return new SingleClientConnManager(schemeRegistry);
+    }
 }
 

+ 13 - 12
pwm/servlet/src/password/pwm/http/servlet/ActivateUserServlet.java

@@ -336,12 +336,13 @@ public class ActivateUserServlet extends PwmServlet {
                 final List<ActionConfiguration> configValues = config.readSettingAsAction(PwmSetting.ACTIVATE_USER_PRE_WRITE_ATTRIBUTES);
                 if (configValues != null && !configValues.isEmpty()) {
                     final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
-                    final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                    settings.setExpandPwmMacros(true);
-                    settings.setMacroMachine(macroMachine);
-                    settings.setUserIdentity(userIdentity);
-                    final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                    actionExecutor.executeActions(configValues, settings, pwmSession);
+
+                    final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                            .setExpandPwmMacros(true)
+                            .setMacroMachine(macroMachine)
+                            .createActionExecutor();
+
+                    actionExecutor.executeActions(configValues, pwmSession);
                 }
             }
 
@@ -380,12 +381,12 @@ public class ActivateUserServlet extends PwmServlet {
 
                             final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication);
                             final List<ActionConfiguration> configValues = pwmApplication.getConfig().readSettingAsAction(PwmSetting.ACTIVATE_USER_POST_WRITE_ATTRIBUTES);
-                            final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                            settings.setExpandPwmMacros(true);
-                            settings.setMacroMachine(macroMachine);
-                            settings.setUserIdentity(userIdentity);
-                            final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                            actionExecutor.executeActions(configValues, settings, pwmSession);
+
+                            final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                                    .setExpandPwmMacros(true)
+                                    .setMacroMachine(macroMachine)
+                                    .createActionExecutor();
+                            actionExecutor.executeActions(configValues, pwmSession);
                         }
                     } catch (PwmOperationalException e) {
                         final ErrorInformation info = new ErrorInformation(PwmError.ERROR_ACTIVATION_FAILURE, e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues());

+ 0 - 4
pwm/servlet/src/password/pwm/http/servlet/CommandServlet.java

@@ -39,7 +39,6 @@ import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.util.Date;
 
@@ -70,9 +69,6 @@ public class CommandServlet extends PwmServlet {
         final String action = pwmRequest.readParameterAsString(PwmConstants.PARAM_ACTION_REQUEST);
         LOGGER.trace(pwmSession, "received request for action " + action);
 
-        final HttpServletRequest req = pwmRequest.getHttpServletRequest();
-        //final HttpServletResponse resp = pwmRequest.getHttpServletResponse();
-
         if (action.equalsIgnoreCase("idleUpdate")) {
             processIdleUpdate(pwmRequest);
         } else if (action.equalsIgnoreCase("checkResponses") || action.equalsIgnoreCase("checkIfResponseConfigNeeded")) {

+ 7 - 10
pwm/servlet/src/password/pwm/http/servlet/ForgottenPasswordServlet.java

@@ -262,7 +262,7 @@ public class ForgottenPasswordServlet extends PwmServlet {
         final ForgottenPasswordBean forgottenPasswordBean = pwmRequest.getPwmSession().getForgottenPasswordBean();
         final String requestedChoiceStr = pwmRequest.readParameterAsString("choice");
         final LinkedHashSet<RecoveryVerificationMethod> remainingAvailableOptionalMethods = new LinkedHashSet<>(figureRemainingAvailableOptionalAuthMethods(forgottenPasswordBean));
-        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.AvailableAuthMethods,remainingAvailableOptionalMethods);
+        pwmRequest.setAttribute(PwmConstants.REQUEST_ATTR.AvailableAuthMethods, remainingAvailableOptionalMethods);
 
         RecoveryVerificationMethod requestedChoice = null;
         if (requestedChoiceStr != null && !requestedChoiceStr.isEmpty()) {
@@ -969,13 +969,12 @@ public class ForgottenPasswordServlet extends PwmServlet {
                         final ChaiUser proxiedUser = pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity);
                         LOGGER.debug(pwmSession, "executing post-forgotten password configured actions to user " + proxiedUser.getEntryDN());
                         final List<ActionConfiguration> configValues = pwmRequest.getConfig().readSettingAsAction(PwmSetting.FORGOTTEN_USER_POST_ACTIONS);
-                        final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                        settings.setExpandPwmMacros(true);
-                        settings.setMacroMachine(
-                                pwmSession.getSessionManager().getMacroMachine(pwmRequest.getPwmApplication()));
-                        settings.setUserIdentity(userIdentity);
-                        final ActionExecutor actionExecutor = new ActionExecutor(pwmRequest.getPwmApplication());
-                        actionExecutor.executeActions(configValues, settings, pwmSession);
+                        final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmRequest.getPwmApplication(),userIdentity)
+                                .setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmRequest.getPwmApplication()))
+                                .setExpandPwmMacros(true)
+                                .createActionExecutor();
+
+                        actionExecutor.executeActions(configValues, pwmSession);
                     }
                 } catch (PwmOperationalException e) {
                     final ErrorInformation info = new ErrorInformation(PwmError.ERROR_UNKNOWN, e.getErrorInformation().getDetailedErrorMsg(), e.getErrorInformation().getFieldValues());
@@ -1422,8 +1421,6 @@ public class ForgottenPasswordServlet extends PwmServlet {
             throw new PwmOperationalException(errorInformation);
         }
     }
-
-
 }
 
 

+ 8 - 6
pwm/servlet/src/password/pwm/http/servlet/GuestRegistrationServlet.java

@@ -422,12 +422,14 @@ public class GuestRegistrationServlet extends PwmServlet {
                 final List<ActionConfiguration> actions = pwmApplication.getConfig().readSettingAsAction(PwmSetting.GUEST_WRITE_ATTRIBUTES);
                 if (actions != null && !actions.isEmpty()) {
                     final MacroMachine macroMachine = MacroMachine.forUser(pwmRequest, userIdentity);
-                    final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                    settings.setExpandPwmMacros(true);
-                    settings.setMacroMachine(macroMachine);
-                    settings.setChaiUser(theUser);
-                    final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                    actionExecutor.executeActions(actions, settings, pwmSession);
+
+
+                    final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, theUser)
+                            .setExpandPwmMacros(true)
+                            .setMacroMachine(macroMachine)
+                            .createActionExecutor();
+
+                    actionExecutor.executeActions(actions, pwmSession);
                 }
             }
 

+ 7 - 6
pwm/servlet/src/password/pwm/http/servlet/HelpdeskServlet.java

@@ -244,15 +244,16 @@ public class HelpdeskServlet extends PwmServlet {
         try {
             final UserIdentity userIdentity = helpdeskBean.getHeldpdeskDetailInfo().getUserInfoBean().getUserIdentity();
             final PwmSession pwmSession = pwmRequest.getPwmSession();
-            final ActionExecutor actionExecutor = new ActionExecutor(pwmRequest.getPwmApplication());
-            final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
+
             final ChaiUser chaiUser = useProxy ?
                     pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity) :
                     pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication(), userIdentity);
-            settings.setUserIdentity(userIdentity);
-            settings.setChaiUser(chaiUser);
-            settings.setExpandPwmMacros(true);
-            actionExecutor.executeAction(action,settings,pwmRequest.getPwmSession());
+            final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmRequest.getPwmApplication(),chaiUser)
+                    .setExpandPwmMacros(true)
+                    .createActionExecutor();
+
+            actionExecutor.executeAction(action,pwmRequest.getPwmSession());
+
             // mark the event log
             {
                 final HelpdeskAuditRecord auditRecord = pwmRequest.getPwmApplication().getAuditManager().createHelpdeskAuditRecord(

+ 7 - 6
pwm/servlet/src/password/pwm/http/servlet/NewUserServlet.java

@@ -631,12 +631,13 @@ public class NewUserServlet extends PwmServlet {
                     PwmSetting.NEWUSER_WRITE_ATTRIBUTES);
             if (actions != null && !actions.isEmpty()) {
                 LOGGER.debug(pwmSession, "executing configured actions to user " + theUser.getEntryDN());
-                final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                settings.setExpandPwmMacros(true);
-                settings.setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmApplication));
-                settings.setUserIdentity(userIdentity);
-                final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                actionExecutor.executeActions(actions, settings, pwmSession);
+
+                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                        .setExpandPwmMacros(true)
+                        .setMacroMachine(pwmSession.getSessionManager().getMacroMachine(pwmApplication))
+                        .createActionExecutor();
+
+                actionExecutor.executeActions(actions, pwmSession);
             }
         }
 

+ 181 - 76
pwm/servlet/src/password/pwm/http/servlet/PeopleSearchServlet.java

@@ -35,15 +35,13 @@ import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.UserPermission;
-import password.pwm.error.ErrorInformation;
-import password.pwm.error.PwmError;
-import password.pwm.error.PwmOperationalException;
-import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.error.*;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.ldap.*;
 import password.pwm.util.JsonUtil;
+import password.pwm.util.SecureHelper;
 import password.pwm.util.TimeDuration;
 import password.pwm.util.cache.CacheKey;
 import password.pwm.util.cache.CachePolicy;
@@ -71,6 +69,7 @@ public class PeopleSearchServlet extends PwmServlet {
         private Map<String,AttributeDetailBean> detail;
         private String photoURL;
         private boolean hasOrgChart;
+        private String orgChartParentKey;
 
         public String getDisplayName() {
             return displayName;
@@ -111,6 +110,14 @@ public class PeopleSearchServlet extends PwmServlet {
         public void setHasOrgChart(boolean hasOrgChart) {
             this.hasOrgChart = hasOrgChart;
         }
+
+        public String getOrgChartParentKey() {
+            return orgChartParentKey;
+        }
+
+        public void setOrgChartParentKey(String orgChartParentKey) {
+            this.orgChartParentKey = orgChartParentKey;
+        }
     }
 
     public static class AttributeDetailBean implements Serializable {
@@ -193,7 +200,9 @@ public class PeopleSearchServlet extends PwmServlet {
         }
     }
 
-    public static class UserTreeReferenceBean extends UserReferenceBean {
+    public static class UserTreeReferenceBean {
+        public String userKey;
+        public List<String> displayNames = new ArrayList<>();
         public String photoURL;
         public boolean hasMoreNodes;
 
@@ -212,6 +221,22 @@ public class PeopleSearchServlet extends PwmServlet {
         public void setHasMoreNodes(boolean hasMoreNodes) {
             this.hasMoreNodes = hasMoreNodes;
         }
+
+        public String getUserKey() {
+            return userKey;
+        }
+
+        public void setUserKey(String userKey) {
+            this.userKey = userKey;
+        }
+
+        public List<String> getDisplayNames() {
+            return displayNames;
+        }
+
+        public void setDisplayNames(List<String> displayNames) {
+            this.displayNames = displayNames;
+        }
     }
 
     public static class UserReferenceBean implements Serializable {
@@ -378,7 +403,7 @@ public class PeopleSearchServlet extends PwmServlet {
         final CacheKey cacheKey = CacheKey.makeCacheKey(
                 this.getClass(),
                 useProxy ? null : pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity(),
-                "search-" + username
+                "search-" + SecureHelper.hash(username, SecureHelper.HashAlgorithm.SHA1)
         );
         {
             final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
@@ -449,11 +474,7 @@ public class PeopleSearchServlet extends PwmServlet {
 
         final RestResultBean restResultBean = new RestResultBean(outputData);
         pwmRequest.outputJsonResult(restResultBean);
-        final long maxCacheSeconds = pwmRequest.getConfig().readSettingAsLong(PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS);
-        if (maxCacheSeconds > 0) {
-            final Date expiration = new Date(System.currentTimeMillis() * maxCacheSeconds * 1000);
-            pwmRequest.getPwmApplication().getCacheService().put(cacheKey, CachePolicy.makePolicy(expiration), JsonUtil.serialize(outputData));
-        }
+        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, outputData);
 
         if (pwmRequest.getPwmApplication().getStatisticsManager() != null) {
             pwmRequest.getPwmApplication().getStatisticsManager().incrementValue(Statistic.PEOPLESEARCH_SEARCHES);
@@ -502,13 +523,12 @@ public class PeopleSearchServlet extends PwmServlet {
     private void restUserTreeData(
             final PwmRequest pwmRequest
     )
-            throws IOException, PwmUnrecoverableException, ServletException
-    {
+            throws IOException, PwmUnrecoverableException, ServletException {
         if (!orgChartIsEnabled(pwmRequest.getConfig())) {
             throw new PwmUnrecoverableException(PwmError.ERROR_SERVICE_NOT_AVAILABLE);
         }
 
-        final Map<String,String> requestInputMap = pwmRequest.readBodyAsJsonStringMap();
+        final Map<String, String> requestInputMap = pwmRequest.readBodyAsJsonStringMap();
         if (requestInputMap == null) {
             return;
         }
@@ -517,43 +537,80 @@ public class PeopleSearchServlet extends PwmServlet {
             return;
         }
         final boolean asParent = Boolean.parseBoolean(requestInputMap.get("asParent"));
+        final UserIdentity userIdentity = UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getConfig());
 
-        final String parentAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-        final String childAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
+        final UserIdentity parentIdentity;
         try {
-
-            UserDetailBean parentDetail = null;
             if (asParent) {
-                parentDetail = makeUserDetailRequestImpl(pwmRequest, userKey);
+                parentIdentity = userIdentity;
             } else {
-                final UserDetailBean selfDetailData = makeUserDetailRequestImpl(pwmRequest, userKey);
-                if (selfDetailData.getDetail().containsKey(parentAttribute)) {
-                    final UserReferenceBean parentReference = selfDetailData.getDetail().get(parentAttribute).getUserReferences().iterator().next();
-                    parentDetail = makeUserDetailRequestImpl(pwmRequest, parentReference.getUserKey());
-                }
+                final UserDetailBean userDetailBean = makeUserDetailRequestImpl(pwmRequest, userKey);
+                parentIdentity = UserIdentity.fromObfuscatedKey(userDetailBean.getOrgChartParentKey(), pwmRequest.getConfig());
             }
+
+            final UserTreeData userTreeData = makeUserTreeData(pwmRequest, parentIdentity);
+            pwmRequest.outputJsonResult(new RestResultBean(userTreeData));
+        } catch (PwmException e) {
+            LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
+            pwmRequest.respondWithError(e.getErrorInformation());
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.forChaiError(e.getErrorCode()),e.getMessage()));
+        }
+    }
+
+
+
+    private UserTreeData makeUserTreeData(
+            final PwmRequest pwmRequest,
+            final UserIdentity parentIdentity
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final boolean useProxy = useProxy(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession());
+
+        final CacheKey cacheKey = CacheKey.makeCacheKey(
+                this.getClass(),
+                useProxy ? null : pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity(),
+                "treeData-" + parentIdentity.toObfuscatedKey(pwmRequest.getConfig()));
+        {
+            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
+            if (cachedOutput != null) {
+                return JsonUtil.deserialize(cachedOutput, UserTreeData.class);
+            }
+        }
+
+        final String parentAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
+        final String childAttribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
+
+        try {
             final UserTreeData userTreeData = new UserTreeData();
-            if (parentDetail != null) {
-                final UserTreeReferenceBean managerTreeReference = userDetailToTreeReference(parentDetail, parentAttribute);
-                userTreeData.setParent(managerTreeReference);
-
-                if (parentDetail.getDetail() != null) {
-                    if (parentDetail.getDetail().containsKey(childAttribute)) {
-                        final List<UserTreeReferenceBean> siblings = new ArrayList<>();
-                        for (final UserReferenceBean siblingReferenceBean : parentDetail.getDetail().get(childAttribute).getUserReferences()) {
-                            final UserDetailBean siblingDetail = makeUserDetailRequestImpl(pwmRequest, siblingReferenceBean.getUserKey());
-                            final UserTreeReferenceBean siblingTreeReferenceBean = userDetailToTreeReference(siblingDetail, childAttribute);
-                            siblings.add(siblingTreeReferenceBean);
+            final UserTreeReferenceBean parentReference = userDetailToTreeReference(pwmRequest, parentIdentity, parentAttribute);
+            userTreeData.setParent(parentReference);
+
+            final ChaiUser parentUser = getChaiUser(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), parentIdentity);
+            final Set<String> childDNs = parentUser.readMultiStringAttribute(childAttribute);
+            int counter = 0;
+            if (childDNs != null) {
+                final Map<String,UserTreeReferenceBean> sortedSiblings = new TreeMap<>();
+                for (final String childDN : childDNs) {
+                    final UserIdentity childIdentity = new UserIdentity(childDN, parentIdentity.getLdapProfileID());
+                    final UserTreeReferenceBean childReference = userDetailToTreeReference(pwmRequest, childIdentity, childAttribute);
+                    if (childReference != null) {
+                        if (childReference.getDisplayNames() != null && !childReference.getDisplayNames().isEmpty()) {
+                            final String firstDisplayName = childReference.getDisplayNames().iterator().next();
+                            sortedSiblings.put(firstDisplayName, childReference);
+                        } else {
+                            sortedSiblings.put(String.valueOf(counter), childReference);
                         }
-                        userTreeData.setSiblings(siblings);
+                        counter++;
                     }
                 }
+                userTreeData.setSiblings(new ArrayList<>(sortedSiblings.values()));
             }
-            pwmRequest.outputJsonResult(new RestResultBean(userTreeData));
-        } catch (PwmOperationalException e) {
-            LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
-            pwmRequest.respondWithError(e.getErrorInformation());
-        } catch (ChaiUnavailableException e) {
+            storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, userTreeData);
+            return userTreeData;
+        } catch (ChaiException e) {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.forChaiError(e.getErrorCode()),e.getMessage()));
         }
     }
@@ -598,7 +655,7 @@ public class PeopleSearchServlet extends PwmServlet {
         final CacheKey cacheKey = CacheKey.makeCacheKey(
                 this.getClass(),
                 useProxy ? null : pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity(),
-                "detail-" + userIdentity.toDelimitedKey()
+                "detail-" + userIdentity.toObfuscatedKey(pwmRequest.getConfig())
         );
         {
             final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
@@ -633,7 +690,7 @@ public class PeopleSearchServlet extends PwmServlet {
                 detailFormConfig, searchResults);
 
         userDetailBean.setDetail(attributeBeans);
-        final String photoURL = figurePhotoURL(pwmRequest.getPwmApplication(), pwmRequest, userIdentity);
+        final String photoURL = figurePhotoURL(pwmRequest, userIdentity);
         if (photoURL != null) {
             userDetailBean.setPhotoURL(photoURL);
         }
@@ -644,31 +701,43 @@ public class PeopleSearchServlet extends PwmServlet {
 
         if (orgChartIsEnabled(pwmRequest.getConfig())) {
             final String parentAttr = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-            if (searchResults.containsKey(parentAttr)) {
+            final String parentDN = searchResults.get(parentAttr);
+            if (parentDN != null && !parentDN.isEmpty()) {
                 userDetailBean.setHasOrgChart(true);
+                final UserIdentity parentIdentity = new UserIdentity(parentDN,userIdentity.getLdapProfileID());
+                userDetailBean.setOrgChartParentKey(parentIdentity.toObfuscatedKey(pwmRequest.getConfig()));
             }
         }
 
         LOGGER.debug(pwmRequest.getPwmSession(), "finished non-cached rest detail request in " + TimeDuration.fromCurrent(
                 startTime).asCompactString() + ", results=" + JsonUtil.serialize(userDetailBean));
 
-        final long maxCacheSeconds = pwmRequest.getConfig().readSettingAsLong(PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS);
-        if (maxCacheSeconds > 0) {
-            final Date expiration = new Date(System.currentTimeMillis() * maxCacheSeconds * 1000);
-            pwmRequest.getPwmApplication().getCacheService().put(cacheKey, CachePolicy.makePolicy(expiration),
-                    JsonUtil.serialize(userDetailBean));
-        }
-
+        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, userDetailBean);
         StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_SEARCHES);
         return userDetailBean;
     }
 
-    private static String figurePhotoURL(
+    private static void storeDataInCache(
             final PwmApplication pwmApplication,
+            final CacheKey cacheKey,
+            final Serializable data
+    )
+            throws PwmUnrecoverableException
+    {
+        final long maxCacheSeconds = pwmApplication.getConfig().readSettingAsLong(PwmSetting.PEOPLE_SEARCH_MAX_CACHE_SECONDS);
+        if (maxCacheSeconds > 0) {
+            final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(maxCacheSeconds * 1000);
+            pwmApplication.getCacheService().put(cacheKey, cachePolicy, JsonUtil.serialize(data));
+        }
+    }
+
+    private static String figurePhotoURL(
             final PwmRequest pwmRequest,
             final UserIdentity userIdentity
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException {
+            throws PwmUnrecoverableException
+    {
+        final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
         final List<UserPermission> showPhotoPermission = pwmApplication.getConfig().readSettingAsUserPermission(PwmSetting.PEOPLE_SEARCH_PHOTO_QUERY_FILTER);
         if (!LdapPermissionTester.testUserPermissions(pwmApplication, pwmRequest.getSessionLabel(), userIdentity, showPhotoPermission)) {
             LOGGER.debug(pwmRequest, "detailed user data lookup for " + userIdentity.toString() + ", failed photo query filter, denying photo view");
@@ -676,16 +745,20 @@ public class PeopleSearchServlet extends PwmServlet {
         }
 
         final String overrideURL = pwmApplication.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
-        if (overrideURL != null && !overrideURL.isEmpty()) {
-            final MacroMachine macroMachine = getMacroMachine(pwmApplication, pwmRequest.getPwmSession(), userIdentity);
-            return macroMachine.expandMacros(overrideURL);
-        }
-
         try {
-            readPhotoDataFromLdap(pwmRequest, userIdentity);
-        } catch (PwmOperationalException e) {
-            LOGGER.debug(pwmRequest, "determined " + userIdentity + " does not have photo data available while generating detail data");
-            return null;
+            if (overrideURL != null && !overrideURL.isEmpty()) {
+                final MacroMachine macroMachine = getMacroMachine(pwmApplication, pwmRequest.getPwmSession(), userIdentity);
+                return macroMachine.expandMacros(overrideURL);
+            }
+
+            try {
+                readPhotoDataFromLdap(pwmRequest, userIdentity);
+            } catch (PwmOperationalException e) {
+                LOGGER.debug(pwmRequest, "determined " + userIdentity + " does not have photo data available while generating detail data");
+                return null;
+            }
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(PwmError.forChaiError(e.getErrorCode()));
         }
 
         return "PeopleSearch?processAction=photo&userKey=" + userIdentity.toObfuscatedKey(pwmApplication.getConfig());
@@ -696,10 +769,10 @@ public class PeopleSearchServlet extends PwmServlet {
             final PwmSession pwmSession,
             final UserIdentity userIdentity
     )
-            throws PwmUnrecoverableException, ChaiUnavailableException {
+            throws PwmUnrecoverableException, ChaiUnavailableException
+    {
         final MacroMachine macroMachine = getMacroMachine(pwmApplication, pwmSession, userIdentity);
-        final String settingValue = pwmApplication.getConfig().readSettingAsString(
-                PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME);
+        final String settingValue = pwmApplication.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME);
         return macroMachine.expandMacros(settingValue);
     }
 
@@ -725,8 +798,7 @@ public class PeopleSearchServlet extends PwmServlet {
             return;
         }
 
-        LOGGER.info(pwmRequest, "received user photo request by "
-                + pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity().toString() + " for user " + userIdentity.toString());
+        LOGGER.info(pwmRequest, "received user photo request to view user " + userIdentity.toString());
 
         final PhotoData photoData;
         try {
@@ -740,9 +812,14 @@ public class PeopleSearchServlet extends PwmServlet {
 
         OutputStream outputStream = null;
         try {
+            final int expireSeconds = 10 * 60;
             pwmRequest.getPwmResponse().getHttpServletResponse().setContentType(photoData.getMimeType());
+            pwmRequest.getPwmResponse().getHttpServletResponse().setDateHeader("Expires", System.currentTimeMillis() + (expireSeconds * 1000l));
+            pwmRequest.getPwmResponse().getHttpServletResponse().setHeader("Cache-Control", "public, max-age=" + expireSeconds);
+
             outputStream = pwmRequest.getPwmResponse().getOutputStream();
             outputStream.write(photoData.getContents());
+
         } finally {
             if (outputStream != null) {
                 outputStream.close();
@@ -822,12 +899,12 @@ public class PeopleSearchServlet extends PwmServlet {
             final PwmSession pwmSession,
             final UserIdentity userIdentity
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException {
+            throws PwmUnrecoverableException
+    {
         final boolean useProxy = useProxy(pwmApplication, pwmSession);
         return useProxy
                 ? pwmApplication.getProxiedChaiUser(userIdentity)
                 : pwmSession.getSessionManager().getActor(pwmApplication, userIdentity);
-
     }
 
     private static MacroMachine getMacroMachine(
@@ -835,7 +912,8 @@ public class PeopleSearchServlet extends PwmServlet {
             final PwmSession pwmSession,
             final UserIdentity userIdentity
     )
-            throws ChaiUnavailableException, PwmUnrecoverableException {
+            throws PwmUnrecoverableException
+    {
         final ChaiUser chaiUser = getChaiUser(pwmApplication, pwmSession, userIdentity);
         final UserInfoBean userInfoBean;
         if (Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_DISPLAYNAME_USEALLMACROS))) {
@@ -922,14 +1000,41 @@ public class PeopleSearchServlet extends PwmServlet {
         return Collections.unmodifiableSet(new HashSet<>(searchResultForm));
     }
 
-    private static UserTreeReferenceBean userDetailToTreeReference(final UserDetailBean userDetailBean, final String nextNodeAttribute) {
+    private static UserTreeReferenceBean userDetailToTreeReference(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity,
+            final String nextNodeAttribute
+    )
+            throws PwmUnrecoverableException
+    {
         final UserTreeReferenceBean userTreeReferenceBean = new UserTreeReferenceBean();
-        userTreeReferenceBean.setUserKey(userDetailBean.getUserKey());
-        userTreeReferenceBean.setPhotoURL(userDetailBean.getPhotoURL());
-        userTreeReferenceBean.setDisplayName(userDetailBean.getDisplayName());
-        if (userDetailBean.getDetail() != null && userDetailBean.getDetail().containsKey(nextNodeAttribute)) {
-            userTreeReferenceBean.setHasMoreNodes(true);
+        userTreeReferenceBean.setUserKey(userIdentity.toObfuscatedKey(pwmRequest.getConfig()));
+        userTreeReferenceBean.setPhotoURL(figurePhotoURL(pwmRequest, userIdentity));
+
+        {
+            final List<String> displayLabels = new ArrayList<>();
+            final List<String> displayStringSettings = pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_ORGCHART_DISPLAY_VALUES);
+            if (displayStringSettings != null) {
+                final MacroMachine macroMachine = getMacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), userIdentity);
+                for (final String displayStringSetting : displayStringSettings) {
+                    final String displayLabel = macroMachine.expandMacros(displayStringSetting);
+                    displayLabels.add(displayLabel);
+                }
+            }
+            userTreeReferenceBean.setDisplayNames(displayLabels);
         }
+
+        userTreeReferenceBean.setHasMoreNodes(false);
+        try {
+            final UserDataReader userDataReader = new LdapUserDataReader(userIdentity, getChaiUser(pwmRequest.getPwmApplication(), pwmRequest.getPwmSession(), userIdentity));
+            final String nextNodeValue = userDataReader.readStringAttribute(nextNodeAttribute);
+            if (nextNodeValue != null && !nextNodeValue.isEmpty()) {
+                userTreeReferenceBean.setHasMoreNodes(true);
+            }
+        } catch (ChaiException e) {
+            LOGGER.debug(pwmRequest, "error reading nextNodeAttribute during userTreeReference construction: " + e.getMessage());
+        }
+
         return userTreeReferenceBean;
     }
 

+ 0 - 1
pwm/servlet/src/password/pwm/http/servlet/ResourceFileServlet.java

@@ -271,7 +271,6 @@ public class ResourceFileServlet extends HttpServlet {
         response.reset();
         response.setBufferSize(BUFFER_SIZE);
         response.setDateHeader("Expires", System.currentTimeMillis() + (setting_expireSeconds * 1000l));
-        response.setHeader("Cache-Control", "public, max-age=" + setting_expireSeconds);
         response.setHeader("ETag", nonceValue);
         response.setContentType(contentType);
 

+ 7 - 6
pwm/servlet/src/password/pwm/http/servlet/UpdateProfileServlet.java

@@ -385,12 +385,13 @@ public class UpdateProfileServlet extends PwmServlet {
             final List<ActionConfiguration> actions = pwmApplication.getConfig().readSettingAsAction(PwmSetting.UPDATE_PROFILE_WRITE_ATTRIBUTES);
             if (actions != null && !actions.isEmpty()) {
                 LOGGER.debug(pwmRequest, "executing configured actions to user " + userIdentity);
-                final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                settings.setExpandPwmMacros(true);
-                settings.setChaiUser(pwmApplication.getProxiedChaiUser(userIdentity));
-                settings.setUserIdentity(userIdentity);
-                final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                actionExecutor.executeActions(actions, settings, pwmSession);
+
+
+                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                        .setExpandPwmMacros(true)
+                        .createActionExecutor();
+
+                actionExecutor.executeActions(actions, pwmSession);
             }
         }
 

+ 1 - 1
pwm/servlet/src/password/pwm/i18n/Config.properties

@@ -30,7 +30,7 @@ Button_HideAdvanced=Hide Advanced Settings
 Confirm_LockConfig=Are you sure you want to close the configuration?  After you close the configuration, you must authenticate using your LDAP directory credentials before authenticating, so be sure your LDAP configuration is working properly before closing.
 Confirm_SkipGuide=Are you sure you want to skip the configuration guide?
 Confirm_UploadConfig=Are you sure you wish to overwrite the current running configuration with the selected file?
-Confirm_UploadLocalDB=Are you sure you wish to replace and upload the existing LocalDB contents with a previously explored LocalDB archive file?  <p>This operation may take a long time to complete, depending on the size of the archive.</p><p>During the upload, the application will not be available.</p>
+Confirm_UploadLocalDB=Are you sure you wish to upload and replace the existing LocalDB contents with a previously exported LocalDB archive file?  <p>This operation may take a long time to complete, depending on the size of the archive.</p><p>During the upload, the application will not be available.  If the operation does not complete, the LocalDB will be emptied.</p>
 Confirm_SSLDisable=Are you sure you wish to use a non-secure connection?  Many directories will not allow password operations over non-secure connections.
 Display_AboutTemplates=<p>Templates are used to set default settings to appropriate values for your system configuration type.  Changing the template will only affect setting values that are at their default.  Settings you have modified will not be affected by changing a template.  You can change the the template type at any time.</p>
 Display_ConfigEditorLocales=<p>Edit the display fields presented to users. Whenever a single value is modified for a setting, all values for that setting will be used to override all default locale-specific values for that particular setting. Display keys not modified from the default will use the default display value of the current defaults.</p>

+ 2 - 0
pwm/servlet/src/password/pwm/i18n/Display.java

@@ -54,6 +54,7 @@ public enum Display implements PwmDisplayBundle {
     Button_Login,
     Button_Logout,
     Button_More,
+    Button_OrgChart,
     Button_RecoverPassword,
     Button_Reset,
     Button_Search,
@@ -258,6 +259,7 @@ public enum Display implements PwmDisplayBundle {
     Title_Logout,
     Title_MainPage,
     Title_NewUser,
+    Title_OrgChart,
     Title_PasswordGuide,
     Title_PasswordPolicy,
     Title_PasswordStrength,

+ 1 - 0
pwm/servlet/src/password/pwm/i18n/Display.properties

@@ -247,6 +247,7 @@ Title_Login=Please Log In
 Title_Logout=Logout
 Title_MainPage=Main Menu
 Title_NewUser=New User Registration
+Title_OrgChart=Organizational Chart
 Title_PasswordGuide=Password Guide
 Title_PasswordPolicy=Password Policy
 Title_PasswordStrength=Password Strength

+ 0 - 5
pwm/servlet/src/password/pwm/ldap/LdapUserDataReader.java

@@ -30,7 +30,6 @@ import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.PwmApplication;
 import password.pwm.bean.UserIdentity;
-import password.pwm.error.ErrorInformation;
 import password.pwm.error.PwmError;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.PwmSession;
@@ -63,11 +62,7 @@ public class LdapUserDataReader implements Serializable, UserDataReader {
             throws PwmUnrecoverableException
     {
         final ChaiUser user;
-        try {
             user = pwmApplication.getProxiedChaiUser(userIdentity);
-        } catch (ChaiUnavailableException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,e.getMessage()));
-        }
         return new LdapUserDataReader(userIdentity, user);
     }
 

+ 1 - 1
pwm/servlet/src/password/pwm/util/cache/CachePolicy.java

@@ -42,7 +42,7 @@ public class CachePolicy implements Serializable {
         return policy;
     }
 
-    public static CachePolicy makePolicy(final long expirationMs) {
+    public static CachePolicy makePolicyWithExpirationMS(final long expirationMs) {
         final CachePolicy policy = new CachePolicy();
         final Date expirationDate = new Date(System.currentTimeMillis() + expirationMs);
         policy.expiration = expirationDate;

+ 3 - 0
pwm/servlet/src/password/pwm/util/localdb/LocalDBUtility.java

@@ -107,8 +107,10 @@ public class LocalDBUtility {
 
         final CSVPrinter csvPrinter = Helper.makeCsvPrinter(new GZIPOutputStream(outputStream));
         try {
+            csvPrinter.printComment(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " LocalDB export on " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
             for (LocalDB.DB loopDB : LocalDB.DB.values()) {
                 if (!BACKUP_IGNORE_DBs.contains(loopDB)) {
+                    csvPrinter.printComment("Export of " + loopDB.toString());
                     final LocalDB.LocalDBIterator<String> localDBIterator = localDB.iterator(loopDB);
                     try {
                         while (localDBIterator.hasNext()) {
@@ -124,6 +126,7 @@ public class LocalDBUtility {
             }
         } finally {
             if (csvPrinter != null) {
+                csvPrinter.printComment("export completed at " + PwmConstants.DEFAULT_DATETIME_FORMAT.format(new Date()));
                 csvPrinter.close();
             }
         }

+ 38 - 30
pwm/servlet/src/password/pwm/util/operations/ActionExecutor.java

@@ -49,26 +49,25 @@ public class ActionExecutor {
     private static final PwmLogger LOGGER = PwmLogger.forClass(ActionExecutor.class);
 
     private PwmApplication pwmApplication;
+    private ActionExecutorSettings settings;
 
-    public ActionExecutor(PwmApplication pwmApplication) {
+    private ActionExecutor(PwmApplication pwmApplication) {
         this.pwmApplication = pwmApplication;
     }
 
     public void executeActions(
             final List<ActionConfiguration> configValues,
-            final ActionExecutorSettings settings,
             final PwmSession pwmSession
     )
             throws ChaiUnavailableException, PwmOperationalException, PwmUnrecoverableException
     {
         for (final ActionConfiguration loopAction : configValues) {
-            this.executeAction(loopAction, settings, pwmSession);
+            this.executeAction(loopAction, pwmSession);
         }
     }
 
     public void executeAction(
             final ActionConfiguration actionConfiguration,
-            final ActionExecutorSettings actionExecutorSettings,
             final PwmSession pwmSession
     )
             throws ChaiUnavailableException, PwmOperationalException, PwmUnrecoverableException
@@ -77,11 +76,11 @@ public class ActionExecutor {
 
         switch (actionConfiguration.getType()) {
             case ldap:
-                executeLdapAction(pwmSession, actionConfiguration, actionExecutorSettings);
+                executeLdapAction(pwmSession, actionConfiguration);
                 break;
 
             case webservice:
-                executeWebserviceAction(pwmSession, actionConfiguration, actionExecutorSettings);
+                executeWebserviceAction(pwmSession, actionConfiguration);
                 break;
         }
 
@@ -90,8 +89,7 @@ public class ActionExecutor {
 
     private void executeLdapAction(
             final PwmSession pwmSession,
-            final ActionConfiguration actionConfiguration,
-            final ActionExecutorSettings settings
+            final ActionConfiguration actionConfiguration
     )
             throws ChaiUnavailableException, PwmOperationalException, PwmUnrecoverableException
     {
@@ -122,8 +120,7 @@ public class ActionExecutor {
 
     private void executeWebserviceAction(
             final PwmSession pwmSession,
-            final ActionConfiguration actionConfiguration,
-            final ActionExecutorSettings settings
+            final ActionConfiguration actionConfiguration
     )
             throws PwmOperationalException, PwmUnrecoverableException
     {
@@ -250,47 +247,58 @@ public class ActionExecutor {
     }
 
     public static class ActionExecutorSettings {
-        private MacroMachine macroMachine;
-        private ChaiUser chaiUser;
-        private UserIdentity userIdentity;
+        private final UserIdentity userIdentity;
+        private final PwmApplication pwmApplication;
+        private final ChaiUser chaiUser;
+
         private boolean expandPwmMacros = true;
+        private MacroMachine macroMachine;
 
-        public boolean isExpandPwmMacros() {
-            return expandPwmMacros;
+        public ActionExecutorSettings(PwmApplication pwmApplication, ChaiUser chaiUser) {
+            this.pwmApplication = pwmApplication;
+            this.chaiUser = chaiUser;
+            this.userIdentity = null;
         }
 
-        public void setExpandPwmMacros(boolean expandPwmMacros) {
-            this.expandPwmMacros = expandPwmMacros;
+        public ActionExecutorSettings(PwmApplication pwmApplication, UserIdentity userIdentity) {
+            this.pwmApplication = pwmApplication;
+            this.userIdentity = userIdentity;
+            this.chaiUser = null;
         }
 
-        public ChaiUser getChaiUser()
-        {
-            return chaiUser;
+        private boolean isExpandPwmMacros() {
+            return expandPwmMacros;
         }
 
-        public void setChaiUser(ChaiUser chaiUser)
+        private ChaiUser getChaiUser()
         {
-            this.chaiUser = chaiUser;
+            return chaiUser;
         }
 
-        public MacroMachine getMacroMachine()
+        private MacroMachine getMacroMachine()
         {
             return macroMachine;
         }
 
-        public void setMacroMachine(MacroMachine macroMachine)
+        private UserIdentity getUserIdentity()
         {
-            this.macroMachine = macroMachine;
+            return userIdentity;
         }
 
-        public UserIdentity getUserIdentity()
-        {
-            return userIdentity;
+        public ActionExecutorSettings setExpandPwmMacros(boolean expandPwmMacros) {
+            this.expandPwmMacros = expandPwmMacros;
+            return this;
         }
 
-        public void setUserIdentity(UserIdentity userIdentity)
+
+        public ActionExecutorSettings setMacroMachine(MacroMachine macroMachine)
         {
-            this.userIdentity = userIdentity;
+            this.macroMachine = macroMachine;
+            return this;
+        }
+
+        public ActionExecutor createActionExecutor() {
+            return new ActionExecutor(this.pwmApplication);
         }
     }
 

+ 18 - 20
pwm/servlet/src/password/pwm/util/operations/PasswordUtility.java

@@ -363,13 +363,11 @@ public class PasswordUtility {
                         pwmSession.getSessionManager().getUserDataReader(pwmApplication)
                 );
 
-                final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                settings.setExpandPwmMacros(true);
-                settings.setUserIdentity(uiBean.getUserIdentity());
-                settings.setMacroMachine(macroMachine);
-
-                final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                actionExecutor.executeActions(configValues, settings, pwmSession);
+                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, uiBean.getUserIdentity())
+                        .setMacroMachine(macroMachine)
+                        .setExpandPwmMacros(true)
+                        .createActionExecutor();
+                actionExecutor.executeActions(configValues, pwmSession);
             }
         }
 
@@ -454,17 +452,18 @@ public class PasswordUtility {
             actions.addAll(pwmApplication.getConfig().readSettingAsAction(PwmSetting.CHANGE_PASSWORD_WRITE_ATTRIBUTES));
             actions.addAll(helpdeskProfile.readSettingAsAction(PwmSetting.HELPDESK_POST_SET_PASSWORD_WRITE_ATTRIBUTES));
             if (!actions.isEmpty()) {
-                final ActionExecutor.ActionExecutorSettings settings = new ActionExecutor.ActionExecutorSettings();
-                settings.setExpandPwmMacros(true);
-                settings.setMacroMachine(MacroMachine.forUser(
-                        pwmApplication,
-                        pwmSession.getSessionStateBean().getLocale(),
-                        sessionLabel,
-                        userIdentity
-                ));
-                settings.setUserIdentity(userIdentity);
-                final ActionExecutor actionExecutor = new ActionExecutor(pwmApplication);
-                actionExecutor.executeActions(actions,settings,pwmSession);
+
+                final ActionExecutor actionExecutor = new ActionExecutor.ActionExecutorSettings(pwmApplication, userIdentity)
+                        .setMacroMachine(MacroMachine.forUser(
+                                pwmApplication,
+                                pwmSession.getSessionStateBean().getLocale(),
+                                sessionLabel,
+                                userIdentity
+                        ))
+                        .setExpandPwmMacros(true)
+                        .createActionExecutor();
+
+                actionExecutor.executeActions(actions,pwmSession);
             }
         }
 
@@ -536,7 +535,6 @@ public class PasswordUtility {
             ChaiProvider loopProvider = null;
             try {
                 loopProvider = ChaiProviderFactory.createProvider(loopConfiguration);
-                final ChaiUser loopUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), loopProvider);
                 final Date lastModifiedDate = determinePwdLastModified(pwmApplication, sessionLabel, userIdentity);
                 returnValue.put(loopReplicaUrl, lastModifiedDate);
             } catch (ChaiUnavailableException e) {
@@ -818,7 +816,7 @@ public class PasswordUtility {
         final CachePolicy cachePolicy;
         {
             final long cacheLifetimeMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.CACHE_PWRULECHECK_LIFETIME_MS));
-            cachePolicy = CachePolicy.makePolicy(cacheLifetimeMS);
+            cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheLifetimeMS);
         }
 
         if (password == null) {

+ 1 - 1
pwm/servlet/web/public/resources/configStyle.css

@@ -231,7 +231,7 @@ table {
     width: 580px;
     overflow-y: auto;
     position: fixed;
-    margin-left: 110px;
+    margin-left: 120px;
     margin-right: 110px;
     z-index: 10;
     background: white;

+ 16 - 11
pwm/servlet/web/public/resources/js/configeditor-settings.js

@@ -236,16 +236,21 @@ StringArrayValueHandler.draw = function(settingKey) {
         })(i);
     }
 
-    var addItemButton = document.createElement("button");
-    addItemButton.setAttribute("type", "button");
-    addItemButton.setAttribute("class","btn");
-    addItemButton.setAttribute("id","button-" + settingKey + "-addItem");
-    addItemButton.innerHTML = '<span class="btn-icon fa fa-plus-square"></span>' + (syntax == 'PROFILE' ? "Add Profile" : "Add Value");
-    parentDivElement.appendChild(addItemButton);
-
-    PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem','click',function(){
-        StringArrayValueHandler.valueHandler(settingKey,-1);
-    });
+    var settingOptions = PWM_SETTINGS['settings'][settingKey]['options'];
+    if (settingOptions && 'max' in settingOptions && itemCount >= settingOptions['max']) {
+        // item count is already maxed out
+    } else {
+        var addItemButton = document.createElement("button");
+        addItemButton.setAttribute("type", "button");
+        addItemButton.setAttribute("class", "btn");
+        addItemButton.setAttribute("id", "button-" + settingKey + "-addItem");
+        addItemButton.innerHTML = '<span class="btn-icon fa fa-plus-square"></span>' + (syntax == 'PROFILE' ? "Add Profile" : "Add Value");
+        parentDivElement.appendChild(addItemButton);
+
+        PWM_MAIN.addEventHandler('button-' + settingKey + '-addItem', 'click', function () {
+            StringArrayValueHandler.valueHandler(settingKey, -1);
+        });
+    }
 };
 
 StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCount, parentDivElement) {
@@ -2420,7 +2425,7 @@ NumericValueHandler.impl = function(settingKey, type, defaultMin, defaultMax) {
         PWM_VAR['clientSettingCache'][settingKey] = data;
         PWM_MAIN.addEventHandler('value_' + settingKey,'input',function(){
             var value = PWM_MAIN.getObject('value_' + settingKey).value;
-            var valid = value.match(/[0-9]+/);
+            var valid = value && value.match(/[0-9]+/);
             if (valid) {
                 console.log('valid');
                 PWM_VAR['clientSettingCache'][settingKey] = data;

+ 46 - 43
pwm/servlet/web/public/resources/js/peoplesearch.js

@@ -51,7 +51,8 @@ PWM_PS.processPeopleSearch = function() {
             var sizeExceeded = data['data']['sizeExceeded'];
             grid.refresh();
             grid.renderArray(gridData);
-            grid.set("sort", { attribute : 'givenName'});
+            var sortState = grid.get("sort");
+            grid.set("sort", sortState);
 
 
             if (sizeExceeded) {
@@ -212,68 +213,71 @@ PWM_PS.showUserDetail = function(userKey) {
 };
 
 PWM_PS.convertUserTreeDataToOrgChartHtml = function(data) {
-    var htmlOutput = '<div>';
-    if ('parent' in data) {
-        var parentReference = data['parent'];
-        htmlOutput += '<div class="panel-orgChart-parent">';
+
+    var makePanel = function(parentReference, type, direction) {
+        var userKey = parentReference['userKey'];
+
+        var output = '';
+        output += '<div class="panel-orgChart-' + type + '">';
+        output += '<div class="panel-orgChart-person">';
         if (parentReference['hasMoreNodes']) {
-            htmlOutput += '<a id="link-parent-' + parentReference['userKey'] + '"><span class="fa fa-arrow-up"/> </a>';
+            output += '<a id="link-' + userKey + '"><span class="icon-orgChart-' + direction + ' fa fa-arrow-' + direction + '"/> </a>';
         }
-        htmlOutput += '<div class="panel-orgChart-person">';
         if ('photoURL' in parentReference) {
-            htmlOutput += '<img class="img-orgChart" id="" src="' + parentReference['photoURL'] + '">';
+            output += '<img class="img-orgChart" id="" src="' + parentReference['photoURL'] + '">';
         }
-        htmlOutput += '<div class="panel-orgChart-displayName">' + parentReference['displayName'] + '</div>';
-        htmlOutput += ' <span id="button-userDetail-' + parentReference['userKey'] + '" class="btn-icon fa fa-info-circle">';
-        htmlOutput += '</div></div><br/>';
+        output += '<div class="panel-orgChart-displayNames">';
+        var loopID = 1;
+        for (var iter in parentReference['displayNames']) {
+            (function(displayName){
+                output += '<div class="panel-orgChart-displayName-' + loopID + '">';
+                output += parentReference['displayNames'][displayName];
+                output += '</div>';
+                loopID++;
+            })(iter);
+        }
+        output += '</div>';
+        output += ' <span id="button-userDetail-' + userKey + '" class="btn-icon fa fa-info-circle">';
+        output += '</div></div>';
+        return output;
+    };
+
+
+    var htmlOutput = '';
+    if ('parent' in data) {
+        htmlOutput += makePanel(data['parent'], 'parent', 'up');
     }
     if ('siblings' in data) {
         for (var iter in data['siblings']) {
             (function(iterCount){
                 var siblingReference = data['siblings'][iterCount];
-                htmlOutput += '<div class="panel-orgChart-child">';
-                if (siblingReference['hasMoreNodes']) {
-                    htmlOutput += '<a id="link-sibling-' + siblingReference['userKey'] + '"><span class="fa fa-arrow-down"/> </a>';
-                }
-                htmlOutput += '<div class="panel-orgChart-person">';
-                if ('photoURL' in siblingReference) {
-                    htmlOutput += '<img class="img-orgChart" id="img-orgChart-' + siblingReference['userKey'] + '" src="' + siblingReference['photoURL'] + '">';
-                }
-
-                htmlOutput += '<div class="panel-orgChart-displayName">' + siblingReference['displayName'] + '</div>';
-                htmlOutput += ' <span id="button-userDetail-' + siblingReference['userKey'] + '" class="btn-icon fa fa-info-circle">';
-                htmlOutput += '</div></div><br/>';
+                htmlOutput += makePanel(siblingReference, 'sibling', 'down');
             })(iter);
         }
     }
-    htmlOutput += '</div>';
     return htmlOutput;
 };
 
 PWM_PS.applyUserTreeDataToOrgChartEvents = function(data) {
-    if ('parent' in data) {
-        var parentReference = data['parent'];
-        if (parentReference['hasMoreNodes']) {
-            PWM_MAIN.addEventHandler('link-parent-' + parentReference['userKey'], 'click', function () {
-                PWM_PS.showOrgChartView(parentReference['userKey'])
+    var applyEventsToReference = function(reference,asParent) {
+        if (reference['hasMoreNodes']) {
+            PWM_MAIN.addEventHandler('link-' + reference['userKey'], 'click', function () {
+                PWM_PS.showOrgChartView(reference['userKey'],asParent)
             });
         }
-        PWM_MAIN.addEventHandler('button-userDetail-' + parentReference['userKey'],'click',function(){
-            PWM_PS.showUserDetail(parentReference['userKey']);
+        PWM_MAIN.addEventHandler('button-userDetail-' + reference['userKey'],'click',function(){
+            PWM_PS.showUserDetail(reference['userKey']);
         });
+
+    };
+    if ('parent' in data) {
+        applyEventsToReference(data['parent'],false);
     }
     if ('siblings' in data) {
         for (var iter in data['siblings']) {
             (function(iterCount){
                 var siblingReference = data['siblings'][iterCount];
-                if (siblingReference['hasMoreNodes']) {
-                    PWM_MAIN.addEventHandler('link-sibling-' + siblingReference['userKey'], 'click', function () {
-                        PWM_PS.showOrgChartView(siblingReference['userKey'], true)
-                    });
-                }
-                PWM_MAIN.addEventHandler('button-userDetail-' + siblingReference['userKey'],'click',function(){
-                    PWM_PS.showUserDetail(siblingReference['userKey']);
-                });
+                applyEventsToReference(siblingReference,true);
             })(iter);
         }
     }
@@ -298,7 +302,7 @@ PWM_PS.showOrgChartView = function(userKey, asParent) {
                 var htmlBody = PWM_PS.convertUserTreeDataToOrgChartHtml(data['data']);
                 PWM_MAIN.closeWaitDialog();
                 PWM_MAIN.showDialog({
-                    title:PWM_MAIN.showString('Button_OrgChart'),
+                    title:PWM_MAIN.showString('Title_OrgChart'),
                     allowMove:true,
                     text:htmlBody,
                     showClose:true,
@@ -346,14 +350,13 @@ PWM_PS.makeSearchGrid = function(nextFunction) {
                 }
             });
 
+            PWM_VAR['peoplesearch_search_grid'].set("sort", { attribute : 'sn', descending: true});
+
         }
     );
 };
 
 PWM_PS.loadPicture = function(parentDiv,url) {
-    if (url.lastIndexOf('http', 0) !== 0) { // if not absolute url
-        url = PWM_MAIN.addPwmFormIDtoURL(url);
-    }
     require(["dojo/on"], function(on){
         var image = new Image();
         image.setAttribute('id',"userPhotoImage");

+ 7 - 2
pwm/servlet/web/public/resources/style.css

@@ -694,7 +694,8 @@ progress:not([value]) {
 }
 
 .panel-orgChart-person {
-    margin:0;
+    margin-left:auto;
+    margin-right:auto;
     display: inline-block;
     background-color: #D4D4D4;
     padding: 3px;
@@ -705,11 +706,15 @@ progress:not([value]) {
     width:auto;
 }
 
-.panel-orgChart-child {
+.panel-orgChart-sibling {
     display: inline-block;
     margin-left: 25px;
 }
 
+.panel-orgChart-displayName-1 {
+    font-weight: bold;
+}
+
 .img-orgChart {
     height:25px;
     width:25px