浏览代码

peoplesearch rest server updates

Jason Rivard 8 年之前
父节点
当前提交
0a366a7ce4

+ 15 - 6
src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartData.java → src/main/java/password/pwm/http/servlet/peoplesearch/OrgChartDataBean.java

@@ -25,9 +25,10 @@ package password.pwm.http.servlet.peoplesearch;
 import java.io.Serializable;
 import java.util.List;
 
-class OrgChartData implements Serializable {
+class OrgChartDataBean implements Serializable {
     private OrgChartReferenceBean parent;
-    private List<OrgChartReferenceBean> siblings;
+    private OrgChartReferenceBean self;
+    private List<OrgChartReferenceBean> children;
 
     public OrgChartReferenceBean getParent() {
         return parent;
@@ -37,11 +38,19 @@ class OrgChartData implements Serializable {
         this.parent = parent;
     }
 
-    public List<OrgChartReferenceBean> getSiblings() {
-        return siblings;
+    public OrgChartReferenceBean getSelf() {
+        return self;
     }
 
-    public void setSiblings(List<OrgChartReferenceBean> siblings) {
-        this.siblings = siblings;
+    public void setSelf(OrgChartReferenceBean self) {
+        this.self = self;
+    }
+
+    public List<OrgChartReferenceBean> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<OrgChartReferenceBean> children) {
+        this.children = children;
     }
 }

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

@@ -22,14 +22,14 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
-class OrgChartReferenceBean {
+class OrgChartReferenceBean implements Serializable {
     public String userKey;
     public List<String> displayNames = new ArrayList<>();
     public String photoURL;
-    public boolean hasMoreNodes;
 
     public String getPhotoURL() {
         return photoURL;
@@ -39,14 +39,6 @@ class OrgChartReferenceBean {
         this.photoURL = photoURL;
     }
 
-    public boolean isHasMoreNodes() {
-        return hasMoreNodes;
-    }
-
-    public void setHasMoreNodes(boolean hasMoreNodes) {
-        this.hasMoreNodes = hasMoreNodes;
-    }
-
     public String getUserKey() {
         return userKey;
     }

+ 48 - 0
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchClientConfigBean.java

@@ -0,0 +1,48 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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 java.io.Serializable;
+import java.util.Map;
+
+public class PeopleSearchClientConfigBean implements Serializable {
+
+    private Map<String,String> peoplesearch_search_columns;
+    private boolean peoplesearch_enablePhoto;
+
+    public Map<String, String> getPeoplesearch_search_columns() {
+        return peoplesearch_search_columns;
+    }
+
+    public void setPeoplesearch_search_columns(Map<String, String> peoplesearch_search_columns) {
+        this.peoplesearch_search_columns = peoplesearch_search_columns;
+    }
+
+    public boolean isPeoplesearch_enablePhoto() {
+        return peoplesearch_enablePhoto;
+    }
+
+    public void setPeoplesearch_enablePhoto(boolean peoplesearch_enablePhoto) {
+        this.peoplesearch_enablePhoto = peoplesearch_enablePhoto;
+    }
+}

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

@@ -0,0 +1,70 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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 password.pwm.config.Configuration;
+import password.pwm.config.PwmSetting;
+
+class PeopleSearchConfiguration {
+    private final String photoAttribute;
+    private final String photoUrlOverride;
+    private final boolean photosEnabled;
+    private final boolean orgChartEnabled;
+    private final String orgChartParentAttr;
+    private final String orgChartChildAttr;
+
+    PeopleSearchConfiguration(final Configuration configuration) {
+        photoAttribute = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
+        photoUrlOverride = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
+        photosEnabled = (photoAttribute != null && !photoAttribute.isEmpty())
+                || (photoUrlOverride != null && !photoUrlOverride.isEmpty());
+
+        orgChartParentAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
+        orgChartChildAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
+        orgChartEnabled = orgChartParentAttr != null && !orgChartParentAttr.isEmpty() && orgChartChildAttr != null && !orgChartChildAttr.isEmpty();
+    }
+
+    public String getPhotoAttribute() {
+        return photoAttribute;
+    }
+
+    public String getPhotoUrlOverride() {
+        return photoUrlOverride;
+    }
+
+    public boolean isPhotosEnabled() {
+        return photosEnabled;
+    }
+
+    public boolean isOrgChartEnabled() {
+        return orgChartEnabled;
+    }
+
+    public String getOrgChartParentAttr() {
+        return orgChartParentAttr;
+    }
+
+    public String getOrgChartChildAttr() {
+        return orgChartChildAttr;
+    }
+}

+ 644 - 0
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchDataReader.java

@@ -0,0 +1,644 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2016 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 com.novell.ldapchai.ChaiUser;
+import com.novell.ldapchai.exception.ChaiException;
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import com.novell.ldapchai.provider.ChaiProvider;
+import password.pwm.AppProperty;
+import password.pwm.PwmApplication;
+import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
+import password.pwm.bean.UserInfoBean;
+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.http.PwmRequest;
+import password.pwm.i18n.Display;
+import password.pwm.ldap.*;
+import password.pwm.svc.cache.CacheKey;
+import password.pwm.svc.cache.CachePolicy;
+import password.pwm.svc.stats.Statistic;
+import password.pwm.svc.stats.StatisticsManager;
+import password.pwm.util.JsonUtil;
+import password.pwm.util.LocaleHelper;
+import password.pwm.util.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
+import password.pwm.util.macro.MacroMachine;
+import password.pwm.ws.server.RestResultBean;
+
+import javax.naming.directory.SearchResult;
+import javax.servlet.ServletException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URLConnection;
+import java.util.*;
+
+public class PeopleSearchDataReader {
+    final private static PwmLogger LOGGER = PwmLogger.forClass(PeopleSearchDataReader.class);
+
+    final private PwmRequest pwmRequest;
+    final private PeopleSearchConfiguration config;
+
+    public PeopleSearchDataReader(PwmRequest pwmRequest) {
+        this.pwmRequest = pwmRequest;
+        this.config= new PeopleSearchConfiguration(pwmRequest.getConfig());
+    }
+
+    public SearchResultBean makeSearchResultBean(
+            final String searchData
+    )
+            throws PwmUnrecoverableException, ChaiUnavailableException {
+        final CacheKey cacheKey = makeCacheKey(SearchResultBean.class.getSimpleName(), searchData);
+
+        { // try to serve from cache first
+            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
+            if (cachedOutput != null) {
+                final SearchResultBean searchResultBean = JsonUtil.deserialize(cachedOutput, SearchResultBean.class);
+                searchResultBean.setFromCache(true);
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
+                return searchResultBean;
+            } else {
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
+            }
+        }
+
+        // if not in cache, build results from ldap
+        final SearchResultBean searchResultBean = makeSearchResultsImpl(pwmRequest, searchData);
+        searchResultBean.setFromCache(false);
+        StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_SEARCHES);
+        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, searchResultBean);
+        LOGGER.trace(pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + searchData + "'");
+        return searchResultBean;
+    }
+
+    public OrgChartDataBean makeOrgChartData(
+            final UserIdentity userIdentity
+
+    )
+            throws PwmUnrecoverableException
+    {
+        final Date startTime = new Date();
+
+        final CacheKey cacheKey = makeCacheKey(OrgChartDataBean.class.getSimpleName(), userIdentity.toDelimitedKey());
+        { // if value is cached then return;
+            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
+            if (cachedOutput != null) {
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
+                return JsonUtil.deserialize(cachedOutput, OrgChartDataBean.class);
+            } else {
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
+            }
+        }
+
+        final OrgChartDataBean orgChartData = new OrgChartDataBean();
+
+        // make self reference
+        orgChartData.setSelf(makeOrgChartReferenceForIdentity(userIdentity));
+
+        { // make parent reference
+            final List<UserIdentity> parentIdentities = readUserDNAttributeValues(userIdentity, config.getOrgChartParentAttr());
+            if (parentIdentities != null && !parentIdentities.isEmpty()) {
+                final UserIdentity parentIdentity = parentIdentities.iterator().next();
+                orgChartData.setParent(makeOrgChartReferenceForIdentity(parentIdentity));
+            }
+        }
+
+        int childCount = 0;
+        { // make children reference
+            final Map<String,OrgChartReferenceBean> sortedChildren = new TreeMap<>();
+            final List<UserIdentity> childIdentities = readUserDNAttributeValues(userIdentity, config.getOrgChartChildAttr());
+            for (final UserIdentity  childIdentity : childIdentities) {
+                final OrgChartReferenceBean childReference = makeOrgChartReferenceForIdentity(childIdentity);
+                if (childReference != null) {
+                    if (childReference.getDisplayNames() != null && !childReference.getDisplayNames().isEmpty()) {
+                        final String firstDisplayName = childReference.getDisplayNames().iterator().next();
+                        sortedChildren.put(firstDisplayName, childReference);
+                    } else {
+                        sortedChildren.put(String.valueOf(childCount), childReference);
+                    }
+                    childCount++;
+                }
+            }
+            orgChartData.setChildren(Collections.unmodifiableList(new ArrayList<>(sortedChildren.values())));
+        }
+
+        final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
+        LOGGER.trace(pwmRequest, "completed makeOrgChartData in " + totalTime.asCompactString() + " with " + childCount + " children" );
+        return orgChartData;
+    }
+
+    public UserDetailBean makeUserDetailRequest(
+            final String userKey
+    )
+            throws PwmUnrecoverableException, IOException, ServletException, PwmOperationalException, ChaiUnavailableException
+    {
+        final Date startTime = new Date();
+        final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
+
+        final CacheKey cacheKey = makeCacheKey(UserDetailBean.class.getSimpleName(), userIdentity.toDelimitedKey());
+        {
+            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
+            if (cachedOutput != null) {
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
+                return JsonUtil.deserialize(cachedOutput, UserDetailBean.class);
+            } else {
+                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
+            }
+        }
+
+        try {
+            checkIfUserIdentityViewable(userIdentity);
+        } catch (PwmOperationalException e) {
+            LOGGER.error(pwmRequest.getPwmSession(), "error during detail results request while checking if requested userIdentity is within search scope: " + e.getMessage());
+            throw e;
+        }
+
+        final UserSearchEngine.UserSearchResults detailResults = doDetailLookup(userIdentity);
+        final Map<String, String> searchResults = detailResults.getResults().get(userIdentity);
+
+        final UserDetailBean userDetailBean = new UserDetailBean();
+        userDetailBean.setUserKey(userKey);
+        final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
+        final Map<String,AttributeDetailBean> attributeBeans = convertResultMapToBeans(pwmRequest, userIdentity, detailFormConfig, searchResults);
+
+        userDetailBean.setDetail(attributeBeans);
+        final String photoURL = figurePhotoURL(pwmRequest, userIdentity);
+        if (photoURL != null) {
+            userDetailBean.setPhotoURL(photoURL);
+        }
+        final List<String> displayName = figureDisplaynames(pwmRequest, userIdentity);
+        if (displayName != null) {
+            userDetailBean.setDisplayNames(displayName);
+        }
+
+        LOGGER.trace(pwmRequest.getPwmSession(), "finished building userDetail result in " + TimeDuration.fromCurrent(startTime).asCompactString());
+        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, userDetailBean);
+        return userDetailBean;
+    }
+
+    private List<String> readUserMultiAttributeValues(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity,
+            final String attributeName
+    )
+            throws PwmUnrecoverableException
+    {
+
+        final List<String> returnObj = new ArrayList<>();
+
+        final int MAX_VALUES = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_VALUE_MAXCOUNT));
+        final ChaiUser chaiUser = getChaiUser(userIdentity);
+        try {
+            final Set<String> ldapValues = chaiUser.readMultiStringAttribute(attributeName);
+            if (ldapValues != null) {
+                returnObj.addAll(ldapValues);
+            }
+            while (returnObj.size() > MAX_VALUES) {
+                returnObj.remove(returnObj.size() - 1);
+            }
+            return Collections.unmodifiableList(returnObj);
+        } catch (ChaiOperationException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" +  e.getMessage()));
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage()));
+        }
+
+    }
+
+    private CacheKey makeCacheKey(
+            final String operationIdentifer,
+            final String dataIdentifer
+    )
+            throws PwmUnrecoverableException
+    {
+        final UserIdentity userIdentity;
+        if (pwmRequest.isAuthenticated() && !useProxy()) {
+            userIdentity = pwmRequest.getUserInfoIfLoggedIn();
+        } else {
+            userIdentity = null;
+        }
+        final String keyString = operationIdentifer + "|" + pwmRequest.getPwmApplication().getSecureService().hash(dataIdentifer);
+        return CacheKey.makeCacheKey(
+                this.getClass(),
+                userIdentity,
+                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
+    )
+            throws PwmUnrecoverableException
+    {
+        final OrgChartReferenceBean orgChartReferenceBean = new OrgChartReferenceBean();
+        orgChartReferenceBean.setUserKey(userIdentity.toObfuscatedKey(pwmRequest.getPwmApplication()));
+        orgChartReferenceBean.setPhotoURL(figurePhotoURL(pwmRequest, userIdentity));
+
+        final List<String> displayLabels = figureDisplaynames(pwmRequest, userIdentity);
+        orgChartReferenceBean.setDisplayNames(displayLabels);
+
+        return orgChartReferenceBean;
+    }
+
+    private List<UserIdentity> readUserDNAttributeValues(
+            final UserIdentity userIdentity,
+            final String attributeName
+    )
+            throws PwmUnrecoverableException
+    {
+
+        final List<UserIdentity> returnObj = new ArrayList<>();
+
+        final int MAX_VALUES = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_VALUE_MAXCOUNT));
+        final ChaiUser chaiUser = getChaiUser(userIdentity);
+        final Set<String> ldapValues;
+        try {
+            ldapValues = chaiUser.readMultiStringAttribute(attributeName);
+        } catch (ChaiOperationException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" +  e.getMessage()));
+        } catch (ChaiUnavailableException e) {
+            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage()));
+        }
+
+
+        final boolean checkUserDNValues = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_MAX_VALUE_VERIFYUSERDN));
+        for (final String userDN : ldapValues) {
+            final UserIdentity loopIdentity = new UserIdentity(userDN, userIdentity.getLdapProfileID());
+            if (returnObj.size() < MAX_VALUES) {
+                try {
+                    if (checkUserDNValues) {
+                        checkIfUserIdentityViewable(loopIdentity);
+                    }
+                    returnObj.add(loopIdentity);
+                } catch (PwmOperationalException e) {
+                    LOGGER.debug(pwmRequest, "discarding userDN " + userDN + " from attribute " + attributeName + " because it does not match search filter");
+                }
+            } else {
+                LOGGER.trace(pwmRequest, "discarding userDN " + userDN + " from attribute " + attributeName + " because maximum value count has been reached");
+            }
+
+        }
+        return returnObj;
+    }
+
+    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 String figurePhotoURL(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity
+    )
+            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");
+            return null;
+        }
+
+        final String overrideURL = pwmApplication.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
+        try {
+            if (overrideURL != null && !overrideURL.isEmpty()) {
+                final MacroMachine macroMachine = getMacroMachine(userIdentity);
+                return macroMachine.expandMacros(overrideURL);
+            }
+
+            try {
+                readPhotoDataFromLdap(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 PwmUnrecoverableException.fromChaiException(e);
+        }
+
+        return "PeopleSearch?processAction=photo&userKey=" + userIdentity.toObfuscatedKey(pwmApplication);
+    }
+
+    private String figureDisplaynameValue(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final MacroMachine macroMachine = getMacroMachine(userIdentity);
+        final String settingValue = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME);
+        return macroMachine.expandMacros(settingValue);
+    }
+
+    private List<String> figureDisplaynames(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final List<String> displayLabels = new ArrayList<>();
+        final List<String> displayStringSettings = pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_DISPLAY_NAMES_CARD_LABELS);
+        if (displayStringSettings != null) {
+            final MacroMachine macroMachine = getMacroMachine(userIdentity);
+            for (final String displayStringSetting : displayStringSettings) {
+                final String displayLabel = macroMachine.expandMacros(displayStringSetting);
+                displayLabels.add(displayLabel);
+            }
+        }
+        return displayLabels;
+    }
+
+    private Map<String,AttributeDetailBean> convertResultMapToBeans(
+            final PwmRequest pwmRequest,
+            final UserIdentity userIdentity,
+            final List<FormConfiguration> detailForm,
+            final Map<String, String> searchResults
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException
+    {
+        final Set<String> searchAttributes = getSearchAttributes(pwmRequest.getConfig());
+        final Map<String,AttributeDetailBean> returnObj = new LinkedHashMap<>();
+        for (FormConfiguration formConfiguration : detailForm) {
+            if (formConfiguration.isRequired() || searchResults.containsKey(formConfiguration.getName())) {
+                AttributeDetailBean bean = new AttributeDetailBean();
+                bean.setName(formConfiguration.getName());
+                bean.setLabel(formConfiguration.getLabel(pwmRequest.getLocale()));
+                bean.setType(formConfiguration.getType());
+                if (searchAttributes.contains(formConfiguration.getName())) {
+                    if (formConfiguration.getType() != FormConfiguration.Type.userDN) {
+                        bean.setSearchable(true);
+                    }
+                }
+                if (formConfiguration.getType() == FormConfiguration.Type.userDN) {
+                    if (searchResults.containsKey(formConfiguration.getName())) {
+                        final List<UserIdentity> identityValues = readUserDNAttributeValues(userIdentity, formConfiguration.getName());
+                        final TreeMap<String, UserReferenceBean> userReferences = new TreeMap<>();
+                        for (final UserIdentity loopIdentity : identityValues) {
+                            final String displayValue = figureDisplaynameValue(pwmRequest, loopIdentity);
+                            final UserReferenceBean userReference = new UserReferenceBean();
+                            userReference.setUserKey(loopIdentity.toObfuscatedKey(pwmRequest.getPwmApplication()));
+                            userReference.setDisplayName(displayValue);
+                            userReferences.put(displayValue, userReference);
+                        }
+                        bean.setUserReferences(userReferences.values());
+                    }
+                } else {
+                    if (formConfiguration.isMultivalue()) {
+                        bean.setValues(readUserMultiAttributeValues(pwmRequest, userIdentity, formConfiguration.getName()));
+                    } else {
+                        if (searchResults.containsKey(formConfiguration.getName())) {
+                            bean.setValues(Collections.singletonList(searchResults.get(formConfiguration.getName())));
+                        } else {
+                            bean.setValues(Collections.<String>emptyList());
+                        }
+                    }
+                }
+                returnObj.put(formConfiguration.getName(),bean);
+            }
+        }
+        return returnObj;
+    }
+
+
+    private ChaiUser getChaiUser(
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final boolean useProxy = useProxy();
+        return useProxy
+                ? pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity)
+                : pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication(), userIdentity);
+    }
+
+    private MacroMachine getMacroMachine(
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final ChaiUser chaiUser = getChaiUser(userIdentity);
+        final UserInfoBean userInfoBean;
+        if (Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_DISPLAYNAME_USEALLMACROS))) {
+            final Locale locale = pwmRequest.getLocale();
+            final ChaiProvider chaiProvider = pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity).getChaiProvider();
+            userInfoBean = new UserInfoBean();
+            final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
+            userStatusReader.populateUserInfoBean(userInfoBean, locale, userIdentity, chaiProvider);
+        } else {
+            userInfoBean = null;
+        }
+        UserDataReader userDataReader = new LdapUserDataReader(userIdentity, chaiUser);
+        return new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfoBean, null, userDataReader);
+    }
+
+    public void checkIfUserIdentityViewable(
+            final UserIdentity userIdentity
+    )
+            throws  PwmUnrecoverableException, PwmOperationalException
+    {
+        final String filterSetting = getSearchFilter(pwmRequest.getConfig());
+        String filterString = filterSetting.replace(PwmConstants.VALUE_REPLACEMENT_USERNAME, "*");
+        while (filterString.contains("**")) {
+            filterString = filterString.replace("**", "*");
+        }
+
+        final boolean match = LdapPermissionTester.testQueryMatch(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userIdentity, filterString);
+        if (!match) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "requested userDN is not available within configured search filter"));
+        }
+    }
+
+    private static String getSearchFilter(final Configuration configuration) {
+        final String configuredFilter = configuration.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 StringBuilder filter = new StringBuilder();
+        filter.append("(&"); //open AND clause for objectclasses and attributes
+        for (final String objectClass : defaultObjectClasses) {
+            filter.append("(objectClass=").append(objectClass).append(")");
+        }
+        filter.append("(|"); // open OR clause for attributes
+        for (final String searchAttribute : searchAttributes) {
+            filter.append("(").append(searchAttribute).append("=*").append(PwmConstants.VALUE_REPLACEMENT_USERNAME).append("*)");
+        }
+        filter.append(")"); // close OR clause
+        filter.append(")"); // close AND clause
+        return filter.toString();
+    }
+
+    private boolean useProxy() {
+
+        final boolean useProxy = pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY);
+        final boolean publicAccessEnabled = pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC);
+
+        return useProxy || !pwmRequest.isAuthenticated() && publicAccessEnabled;
+    }
+
+    private UserSearchEngine.UserSearchResults doDetailLookup(
+            final UserIdentity userIdentity
+    )
+            throws PwmUnrecoverableException
+    {
+        final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
+        final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
+                detailFormConfig, pwmRequest.getLocale());
+
+        if (config.isOrgChartEnabled()) {
+            final String orgChartParentAttr = config.getOrgChartParentAttr();
+            if (!attributeHeaderMap.containsKey(orgChartParentAttr)) {
+                attributeHeaderMap.put(orgChartParentAttr, orgChartParentAttr);
+            }
+            final String orgChartChildAttr = config.getOrgChartParentAttr();
+            if (!attributeHeaderMap.containsKey(orgChartChildAttr)) {
+                attributeHeaderMap.put(orgChartChildAttr, orgChartChildAttr);
+            }
+        }
+
+        try {
+            final ChaiUser theUser = getChaiUser(userIdentity);
+            final Map<String, String> values = theUser.readStringAttributes(attributeHeaderMap.keySet());
+            return new UserSearchEngine.UserSearchResults(
+                    attributeHeaderMap,
+                    Collections.singletonMap(userIdentity, values),
+                    false
+            );
+        } catch (ChaiException e) {
+            LOGGER.error("unexpected error during detail lookup of '" + userIdentity + "', error: " + e.getMessage());
+            throw PwmUnrecoverableException.fromChaiException(e);
+        }
+    }
+
+    public PhotoDataBean readPhotoDataFromLdap(
+            final UserIdentity userIdentity
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
+    {
+        final String attribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
+        if (attribute == null || attribute.isEmpty()) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "ldap photo attribute is not configured"));
+        }
+
+        byte[] photoData;
+        String mimeType;
+        try {
+            final ChaiUser chaiUser = getChaiUser(userIdentity);
+            final byte[][] photoAttributeData = chaiUser.readMultiByteAttribute(attribute);
+            if (photoAttributeData == null || photoAttributeData.length == 0 || photoAttributeData[0].length == 0) {
+                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "user has no photo data stored in LDAP attribute"));
+            }
+            photoData = photoAttributeData[0];
+            mimeType = URLConnection.guessContentTypeFromStream(new ByteArrayInputStream(photoData));
+        } catch (IOException | ChaiOperationException e) {
+            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN, "error reading user photo ldap attribute: " + e.getMessage()));
+        }
+        return new PhotoDataBean(mimeType, photoData);
+    }
+
+    private SearchResultBean makeSearchResultsImpl(
+            final PwmRequest pwmRequest,
+            final String username
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException
+    {
+        final Date startTime = new Date();
+
+        if (username == null || username.length() < 1) {
+            return new SearchResultBean();
+        }
+
+        final boolean useProxy = useProxy();
+        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
+        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
+        searchConfiguration.setContexts(
+                pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_BASE));
+        searchConfiguration.setEnableContextValidation(false);
+        searchConfiguration.setUsername(username);
+        searchConfiguration.setEnableValueEscaping(false);
+        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig()));
+        if (!useProxy) {
+            searchConfiguration.setLdapProfile(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity().getLdapProfileID());
+            searchConfiguration.setChaiProvider(pwmRequest.getPwmSession().getSessionManager().getChaiProvider());
+        }
+
+        final UserSearchEngine.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 Locale locale = pwmRequest.getLocale();
+            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm);
+            sizeExceeded = results.isSizeExceeded();
+        } catch (PwmOperationalException e) {
+            final ErrorInformation errorInformation = e.getErrorInformation();
+            LOGGER.error(pwmRequest.getSessionLabel(), errorInformation.toDebugStr());
+            throw new PwmUnrecoverableException(errorInformation);
+        }
+
+        final TimeDuration searchDuration = TimeDuration.fromCurrent(startTime);
+        LOGGER.trace(pwmRequest.getPwmSession(), "finished rest peoplesearch search in " +
+                searchDuration.asCompactString() + " not using cache, size=" + results.getResults().size());
+
+        final SearchResultBean searchResultBean = new SearchResultBean();
+        searchResultBean.setSearchResults(new ArrayList<>(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(),null)));
+        searchResultBean.setSizeExceeded(sizeExceeded);
+        final String aboutMessage = LocaleHelper.getLocalizedMessage(
+                pwmRequest.getLocale(),
+                Display.Display_SearchResultsInfo.getKey(),
+                pwmRequest.getConfig(),
+                Display.class,
+                new String[]{String.valueOf(results.getResults().size()), searchDuration.asLongString(pwmRequest.getLocale())}
+        );
+        searchResultBean.setAboutResultMessage(aboutMessage);
+        return searchResultBean;
+    }
+
+
+}

+ 20 - 655
src/main/java/password/pwm/http/servlet/peoplesearch/PeopleSearchServlet.java

@@ -22,46 +22,29 @@
 
 package password.pwm.http.servlet.peoplesearch;
 
-import com.novell.ldapchai.ChaiUser;
-import com.novell.ldapchai.exception.ChaiException;
-import com.novell.ldapchai.exception.ChaiOperationException;
 import com.novell.ldapchai.exception.ChaiUnavailableException;
-import com.novell.ldapchai.provider.ChaiProvider;
 import password.pwm.*;
 import password.pwm.bean.UserIdentity;
-import password.pwm.bean.UserInfoBean;
-import password.pwm.config.Configuration;
 import password.pwm.config.FormConfiguration;
 import password.pwm.config.PwmSetting;
-import password.pwm.config.UserPermission;
 import password.pwm.error.*;
 import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmHttpRequestWrapper;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmRequestFlag;
 import password.pwm.http.servlet.AbstractPwmServlet;
-import password.pwm.i18n.Display;
-import password.pwm.ldap.*;
-import password.pwm.svc.cache.CacheKey;
-import password.pwm.svc.cache.CachePolicy;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.util.JsonUtil;
-import password.pwm.util.LocaleHelper;
-import password.pwm.util.TimeDuration;
 import password.pwm.util.Validator;
 import password.pwm.util.logging.PwmLogger;
-import password.pwm.util.macro.MacroMachine;
 import password.pwm.ws.server.RestResultBean;
 
 import javax.servlet.ServletException;
 import javax.servlet.annotation.WebServlet;
 import javax.servlet.http.HttpServletResponse;
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.Serializable;
-import java.net.URLConnection;
 import java.util.*;
 
 @WebServlet(
@@ -77,6 +60,8 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
 
     private static final PwmLogger LOGGER = PwmLogger.forClass(PeopleSearchServlet.class);
 
+    private static final String PARAM_USERKEY = "userKey";
+
     public enum PeopleSearchActions implements ProcessAction {
         search(HttpMethod.POST),
         detail(HttpMethod.POST),
@@ -179,11 +164,11 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
                     formConfiguration.getLabel(pwmRequest.getLocale()));
         }
 
-        final HashMap<String,Object> returnValues = new HashMap<>();
-        returnValues.put("peoplesearch_search_columns", searchColumns);
-        returnValues.put("peoplesearch_enablePhoto", peopleSearchConfiguration.isPhotosEnabled());
+        final PeopleSearchClientConfigBean peopleSearchClientConfigBean = new PeopleSearchClientConfigBean();
+        peopleSearchClientConfigBean.setPeoplesearch_search_columns(searchColumns);
+        peopleSearchClientConfigBean.setPeoplesearch_enablePhoto(peopleSearchConfiguration.isPhotosEnabled());
 
-        final RestResultBean restResultBean = new RestResultBean(returnValues);
+        final RestResultBean restResultBean = new RestResultBean(peopleSearchClientConfigBean);
         LOGGER.trace(pwmRequest, "returning clientData: " + JsonUtil.serialize(restResultBean));
         pwmRequest.outputJsonResult(restResultBean);
     }
@@ -198,127 +183,17 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
         final Map<String, String> valueMap = JsonUtil.deserializeStringMap(bodyString);
 
         final String username = Validator.sanitizeInputValue(pwmRequest.getConfig(), valueMap.get("username"), 1024);
-        final CacheKey cacheKey = makeCacheKey(pwmRequest, "search", username);
-
-        { // try to serve from cache first
-            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
-            if (cachedOutput != null) {
-                final SearchResultBean searchResultBean = JsonUtil.deserialize(cachedOutput, SearchResultBean.class);
-                searchResultBean.setFromCache(true);
-                pwmRequest.outputJsonResult(new RestResultBean(searchResultBean));
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
-                return;
-            } else {
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
-            }
-        }
 
         // if not in cache, build results from ldap
-        final SearchResultBean searchResultBean = makeSearchResultsImpl(pwmRequest, username);
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
+        final SearchResultBean searchResultBean = peopleSearchDataReader.makeSearchResultBean(username);
         searchResultBean.setFromCache(false);
         final RestResultBean restResultBean = new RestResultBean(searchResultBean);
         pwmRequest.outputJsonResult(restResultBean);
-        StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_SEARCHES);
-        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, searchResultBean);
         LOGGER.trace(pwmRequest, "returning " + searchResultBean.getSearchResults().size() + " results for search request '" + username + "'");
     }
 
-    private SearchResultBean makeSearchResultsImpl(
-            final PwmRequest pwmRequest,
-            final String username
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final Date startTime = new Date();
-
-        if (username == null || username.length() < 1) {
-            return new SearchResultBean();
-        }
-
-        final boolean useProxy = useProxy(pwmRequest);
-        final UserSearchEngine userSearchEngine = new UserSearchEngine(pwmRequest);
-        final UserSearchEngine.SearchConfiguration searchConfiguration = new UserSearchEngine.SearchConfiguration();
-        searchConfiguration.setContexts(
-                pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_SEARCH_BASE));
-        searchConfiguration.setEnableContextValidation(false);
-        searchConfiguration.setUsername(username);
-        searchConfiguration.setEnableValueEscaping(false);
-        searchConfiguration.setFilter(getSearchFilter(pwmRequest.getConfig()));
-        if (!useProxy) {
-            searchConfiguration.setLdapProfile(pwmRequest.getPwmSession().getUserInfoBean().getUserIdentity().getLdapProfileID());
-            searchConfiguration.setChaiProvider(pwmRequest.getPwmSession().getSessionManager().getChaiProvider());
-        }
-
-        final UserSearchEngine.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 Locale locale = pwmRequest.getLocale();
-            results = userSearchEngine.performMultiUserSearchFromForm(locale, searchConfiguration, maxResults, searchForm);
-            sizeExceeded = results.isSizeExceeded();
-        } catch (PwmOperationalException e) {
-            final ErrorInformation errorInformation = e.getErrorInformation();
-            LOGGER.error(pwmRequest.getSessionLabel(), errorInformation.toDebugStr());
-            throw new PwmUnrecoverableException(errorInformation);
-        }
-
-        final TimeDuration searchDuration = TimeDuration.fromCurrent(startTime);
-        LOGGER.trace(pwmRequest.getPwmSession(), "finished rest peoplesearch search in " +
-                searchDuration.asCompactString() + " not using cache, size=" + results.getResults().size());
-
-        final SearchResultBean searchResultBean = new SearchResultBean();
-        searchResultBean.setSearchResults(new ArrayList<>(results.resultsAsJsonOutput(pwmRequest.getPwmApplication(),null)));
-        searchResultBean.setSizeExceeded(sizeExceeded);
-        final String aboutMessage = LocaleHelper.getLocalizedMessage(
-                pwmRequest.getLocale(),
-                Display.Display_SearchResultsInfo.getKey(),
-                pwmRequest.getConfig(),
-                Display.class,
-                new String[]{String.valueOf(results.getResults().size()), searchDuration.asLongString(pwmRequest.getLocale())}
-        );
-        searchResultBean.setAboutResultMessage(aboutMessage);
-        return searchResultBean;
-    }
-
-    private static UserSearchEngine.UserSearchResults doDetailLookup(
-            final PwmRequest pwmRequest,
-            final PeopleSearchConfiguration peopleSearchConfiguration,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final Configuration config = pwmRequest.getConfig();
-        final List<FormConfiguration> detailFormConfig = config.readSettingAsForm(PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
-        final Map<String, String> attributeHeaderMap = UserSearchEngine.UserSearchResults.fromFormConfiguration(
-                detailFormConfig, pwmRequest.getLocale());
-
-        if (peopleSearchConfiguration.orgChartIsEnabled()) {
-            final String orgChartParentAttr = config.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-            if (!attributeHeaderMap.containsKey(orgChartParentAttr)) {
-                attributeHeaderMap.put(orgChartParentAttr, orgChartParentAttr);
-            }
-            final String orgChartChildAttr = config.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
-            if (!attributeHeaderMap.containsKey(orgChartChildAttr)) {
-                attributeHeaderMap.put(orgChartChildAttr, orgChartChildAttr);
-            }
-        }
 
-        try {
-            final ChaiUser theUser = getChaiUser(pwmRequest, userIdentity);
-            final Map<String, String> values = theUser.readStringAttributes(attributeHeaderMap.keySet());
-            return new UserSearchEngine.UserSearchResults(
-                    attributeHeaderMap,
-                    Collections.singletonMap(userIdentity, values),
-                    false
-            );
-        } catch (ChaiException e) {
-            LOGGER.error("unexpected error during detail lookup of '" + userIdentity + "', error: " + e.getMessage());
-            throw PwmUnrecoverableException.fromChaiException(e);
-        }
-    }
 
     private void restOrgChartData(
             final PwmRequest pwmRequest,
@@ -326,7 +201,7 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
     )
             throws IOException, PwmUnrecoverableException, ServletException
     {
-        if (!peopleSearchConfiguration.orgChartIsEnabled()) {
+        if (!peopleSearchConfiguration.isOrgChartEnabled()) {
             throw new PwmUnrecoverableException(PwmError.ERROR_SERVICE_NOT_AVAILABLE);
         }
 
@@ -334,80 +209,23 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
         if (requestInputMap == null) {
             return;
         }
-        final String userKey = requestInputMap.get("userKey");
+        final String userKey = requestInputMap.get(PARAM_USERKEY);
         if (userKey == null || userKey.isEmpty()) {
             return;
         }
-        final boolean asParent = Boolean.parseBoolean(requestInputMap.get("asParent"));
         final UserIdentity userIdentity = UserIdentity.fromObfuscatedKey(userKey, pwmRequest.getPwmApplication());
 
-        final UserIdentity parentIdentity;
         try {
-            if (asParent) {
-                parentIdentity = userIdentity;
-            } else {
-                final UserDetailBean userDetailBean = makeUserDetailRequestImpl(pwmRequest, peopleSearchConfiguration, userKey);
-                parentIdentity = UserIdentity.fromObfuscatedKey(userDetailBean.getOrgChartParentKey(), pwmRequest.getPwmApplication());
-            }
-
-            final OrgChartData orgChartData = makeOrgChartData(pwmRequest, parentIdentity);
+            final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
+            final OrgChartDataBean orgChartData = peopleSearchDataReader.makeOrgChartData(userIdentity);
             pwmRequest.outputJsonResult(new RestResultBean(orgChartData));
             StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_ORGCHART);
         } catch (PwmException e) {
             LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
             pwmRequest.respondWithError(e.getErrorInformation());
-        } catch (ChaiUnavailableException e) {
-            throw PwmUnrecoverableException.fromChaiException(e);
         }
     }
 
-    private OrgChartData makeOrgChartData(
-            final PwmRequest pwmRequest,
-            final UserIdentity parentIdentity
-
-    )
-            throws PwmUnrecoverableException
-    {
-        final Date startTime = new Date();
-
-        final CacheKey cacheKey = makeCacheKey(pwmRequest, "orgChartData", parentIdentity.toDelimitedKey());
-        { // if value is cached then return;
-            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
-            if (cachedOutput != null) {
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
-                return JsonUtil.deserialize(cachedOutput, OrgChartData.class);
-            } else {
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
-            }
-        }
-
-        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 OrgChartData orgChartData = new OrgChartData();
-        final OrgChartReferenceBean parentReference = makeOrgChartReferenceForIdentity(pwmRequest, parentIdentity, parentAttribute);
-        orgChartData.setParent(parentReference);
-
-        final Map<String,OrgChartReferenceBean> sortedSiblings = new TreeMap<>();
-        final List<UserIdentity> childIdentities = readUserDNAttributeValues(pwmRequest, parentIdentity, childAttribute);
-        int counter = 0;
-        for (final UserIdentity  childIdentity : childIdentities) {
-            final OrgChartReferenceBean childReference = makeOrgChartReferenceForIdentity(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);
-                }
-                counter++;
-            }
-            orgChartData.setSiblings(new ArrayList<>(sortedSiblings.values()));
-        }
-        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, orgChartData);
-        LOGGER.trace(pwmRequest, "completed building orgChartData in " + TimeDuration.fromCurrent(startTime).asCompactString());
-        return orgChartData;
-    }
 
     private void restUserDetailRequest(
             final PwmRequest pwmRequest,
@@ -421,183 +239,38 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
             return;
         }
 
-        final String userKey = valueMap.get("userKey");
+        final String userKey = valueMap.get(PARAM_USERKEY);
         if (userKey == null || userKey.isEmpty()) {
             return;
         }
 
         try {
-            final UserDetailBean detailData = makeUserDetailRequestImpl(pwmRequest, peopleSearchConfiguration, userKey);
+            final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
+            final UserDetailBean detailData = peopleSearchDataReader.makeUserDetailRequest(userKey);
             pwmRequest.outputJsonResult(new RestResultBean(detailData));
             pwmRequest.getPwmApplication().getStatisticsManager().incrementValue(Statistic.PEOPLESEARCH_DETAILS);
         } catch (PwmOperationalException e) {
             LOGGER.error(pwmRequest, "error generating user detail object: " + e.getMessage());
             pwmRequest.respondWithError(e.getErrorInformation());
         }
-
-    }
-
-    private UserDetailBean makeUserDetailRequestImpl(
-            final PwmRequest pwmRequest,
-            final PeopleSearchConfiguration peopleSearchConfiguration,
-            final String userKey
-    )
-            throws PwmUnrecoverableException, IOException, ServletException, PwmOperationalException, ChaiUnavailableException
-    {
-        final Date startTime = new Date();
-        final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
-
-        final CacheKey cacheKey = makeCacheKey(pwmRequest, "detail", userIdentity.toDelimitedKey());
-        {
-            final String cachedOutput = pwmRequest.getPwmApplication().getCacheService().get(cacheKey);
-            if (cachedOutput != null) {
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_HITS);
-                return JsonUtil.deserialize(cachedOutput, UserDetailBean.class);
-            } else {
-                StatisticsManager.incrementStat(pwmRequest, Statistic.PEOPLESEARCH_CACHE_MISSES);
-            }
-        }
-
-        try {
-            checkIfUserIdentityViewable(pwmRequest, userIdentity);
-        } catch (PwmOperationalException e) {
-            LOGGER.error(pwmRequest.getPwmSession(), "error during detail results request while checking if requested userIdentity is within search scope: " + e.getMessage());
-            throw e;
-        }
-
-        final UserSearchEngine.UserSearchResults detailResults = doDetailLookup(pwmRequest, peopleSearchConfiguration, userIdentity);
-        final Map<String, String> searchResults = detailResults.getResults().get(userIdentity);
-
-        final UserDetailBean userDetailBean = new UserDetailBean();
-        userDetailBean.setUserKey(userKey);
-        final List<FormConfiguration> detailFormConfig = pwmRequest.getConfig().readSettingAsForm( PwmSetting.PEOPLE_SEARCH_DETAIL_FORM);
-        final Map<String,AttributeDetailBean> attributeBeans = convertResultMapToBeans(pwmRequest, userIdentity, detailFormConfig, searchResults);
-
-        userDetailBean.setDetail(attributeBeans);
-        final String photoURL = figurePhotoURL(pwmRequest, userIdentity);
-        if (photoURL != null) {
-            userDetailBean.setPhotoURL(photoURL);
-        }
-        final List<String> displayName = figureDisplaynames(pwmRequest, userIdentity);
-        if (displayName != null) {
-            userDetailBean.setDisplayNames(displayName);
-        }
-
-        if (peopleSearchConfiguration.orgChartIsEnabled()) {
-            final String parentAttr = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-            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.getPwmApplication()));
-            } else {
-                final String childAttr = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
-                final String childDN = searchResults.get(childAttr);
-                if (childDN != null && !childDN.isEmpty()) {
-                    userDetailBean.setHasOrgChart(true);
-                    // no parent so use self as parent.
-                    userDetailBean.setOrgChartParentKey(userIdentity.toObfuscatedKey(pwmRequest.getPwmApplication()));
-                }
-            }
-        }
-
-        LOGGER.trace(pwmRequest.getPwmSession(), "finished building userDetail result in " + TimeDuration.fromCurrent(startTime).asCompactString());
-        storeDataInCache(pwmRequest.getPwmApplication(), cacheKey, userDetailBean);
-        return userDetailBean;
-    }
-
-    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
-    {
-        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");
-            return null;
-        }
-
-        final String overrideURL = pwmApplication.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
-        try {
-            if (overrideURL != null && !overrideURL.isEmpty()) {
-                final MacroMachine macroMachine = getMacroMachine(pwmRequest, 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 PwmUnrecoverableException.fromChaiException(e);
-        }
-
-        return "PeopleSearch?processAction=photo&userKey=" + userIdentity.toObfuscatedKey(pwmApplication);
     }
 
-    private static String figureDisplaynameValue(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final MacroMachine macroMachine = getMacroMachine(pwmRequest, userIdentity);
-        final String settingValue = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_DISPLAY_NAME);
-        return macroMachine.expandMacros(settingValue);
-    }
-
-    private static List<String> figureDisplaynames(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final List<String> displayLabels = new ArrayList<>();
-        final List<String> displayStringSettings = pwmRequest.getConfig().readSettingAsStringArray(PwmSetting.PEOPLE_SEARCH_DISPLAY_NAMES_CARD_LABELS);
-        if (displayStringSettings != null) {
-            final MacroMachine macroMachine = getMacroMachine(pwmRequest, userIdentity);
-            for (final String displayStringSetting : displayStringSettings) {
-                final String displayLabel = macroMachine.expandMacros(displayStringSetting);
-                displayLabels.add(displayLabel);
-            }
-        }
-        return displayLabels;
-    }
-
-
     private void processUserPhotoImageRequest(final PwmRequest pwmRequest)
             throws ChaiUnavailableException, PwmUnrecoverableException, IOException, ServletException
     {
-        final String userKey = pwmRequest.readParameterAsString("userKey", PwmHttpRequestWrapper.Flag.BypassValidation);
+        final String userKey = pwmRequest.readParameterAsString(PARAM_USERKEY, PwmHttpRequestWrapper.Flag.BypassValidation);
         if (userKey.length() < 1) {
-            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, "userKey parameter is missing");
+            final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_PARAMETER, PARAM_USERKEY + " parameter is missing");
             LOGGER.error(pwmRequest, errorInformation);
             pwmRequest.respondWithError(errorInformation, false);
             return;
         }
 
 
+        final PeopleSearchDataReader peopleSearchDataReader = new PeopleSearchDataReader(pwmRequest);
         final UserIdentity userIdentity = UserIdentity.fromKey(userKey, pwmRequest.getPwmApplication());
         try {
-            checkIfUserIdentityViewable(pwmRequest, userIdentity);
+            peopleSearchDataReader.checkIfUserIdentityViewable(userIdentity);
         } catch (PwmOperationalException e) {
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, "error during photo request while checking if requested userIdentity is within search scope: " + e.getMessage());
             LOGGER.error(pwmRequest, errorInformation);
@@ -609,7 +282,7 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
 
         final PhotoDataBean photoData;
         try {
-            photoData = readPhotoDataFromLdap(pwmRequest, userIdentity);
+            photoData = peopleSearchDataReader.readPhotoDataFromLdap(userIdentity);
         } catch (PwmOperationalException e) {
             final ErrorInformation errorInformation = e.getErrorInformation();
             LOGGER.error(pwmRequest, errorInformation);
@@ -634,312 +307,4 @@ public class PeopleSearchServlet extends AbstractPwmServlet {
             }
         }
     }
-
-    private static Map<String,AttributeDetailBean> convertResultMapToBeans(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity,
-            final List<FormConfiguration> detailForm,
-            final Map<String, String> searchResults
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException
-    {
-        final Set<String> searchAttributes = getSearchAttributes(pwmRequest.getConfig());
-        final Map<String,AttributeDetailBean> returnObj = new LinkedHashMap<>();
-        for (FormConfiguration formConfiguration : detailForm) {
-            if (formConfiguration.isRequired() || searchResults.containsKey(formConfiguration.getName())) {
-                AttributeDetailBean bean = new AttributeDetailBean();
-                bean.setName(formConfiguration.getName());
-                bean.setLabel(formConfiguration.getLabel(pwmRequest.getLocale()));
-                bean.setType(formConfiguration.getType());
-                if (searchAttributes.contains(formConfiguration.getName())) {
-                    if (formConfiguration.getType() != FormConfiguration.Type.userDN) {
-                        bean.setSearchable(true);
-                    }
-                }
-                if (formConfiguration.getType() == FormConfiguration.Type.userDN) {
-                    if (searchResults.containsKey(formConfiguration.getName())) {
-                        final List<UserIdentity> identityValues = readUserDNAttributeValues(pwmRequest, userIdentity, formConfiguration.getName());
-                        final TreeMap<String, UserReferenceBean> userReferences = new TreeMap<>();
-                        for (final UserIdentity loopIdentity : identityValues) {
-                            final String displayValue = figureDisplaynameValue(pwmRequest, loopIdentity);
-                            final UserReferenceBean userReference = new UserReferenceBean();
-                            userReference.setUserKey(loopIdentity.toObfuscatedKey(pwmRequest.getPwmApplication()));
-                            userReference.setDisplayName(displayValue);
-                            userReferences.put(displayValue, userReference);
-                        }
-                        bean.setUserReferences(userReferences.values());
-                    }
-                } else {
-                    if (formConfiguration.isMultivalue()) {
-                        bean.setValues(readUserMultiAttributeValues(pwmRequest, userIdentity, formConfiguration.getName()));
-                    } else {
-                        if (searchResults.containsKey(formConfiguration.getName())) {
-                            bean.setValues(Collections.singletonList(searchResults.get(formConfiguration.getName())));
-                        } else {
-                            bean.setValues(Collections.<String>emptyList());
-                        }
-                    }
-                }
-                returnObj.put(formConfiguration.getName(),bean);
-            }
-        }
-        return returnObj;
-    }
-
-    private static boolean useProxy(final PwmRequest pwmRequest) {
-
-        final boolean useProxy = pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_USE_PROXY);
-        final boolean publicAccessEnabled = pwmRequest.getConfig().readSettingAsBoolean(PwmSetting.PEOPLE_SEARCH_ENABLE_PUBLIC);
-
-        return useProxy || !pwmRequest.isAuthenticated() && publicAccessEnabled;
-
-    }
-
-    private static ChaiUser getChaiUser(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final boolean useProxy = useProxy(pwmRequest);
-        return useProxy
-                ? pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity)
-                : pwmRequest.getPwmSession().getSessionManager().getActor(pwmRequest.getPwmApplication(), userIdentity);
-    }
-
-    private static MacroMachine getMacroMachine(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws PwmUnrecoverableException
-    {
-        final ChaiUser chaiUser = getChaiUser(pwmRequest, userIdentity);
-        final UserInfoBean userInfoBean;
-        if (Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_DISPLAYNAME_USEALLMACROS))) {
-            final Locale locale = pwmRequest.getLocale();
-            final ChaiProvider chaiProvider = pwmRequest.getPwmApplication().getProxiedChaiUser(userIdentity).getChaiProvider();
-            userInfoBean = new UserInfoBean();
-            final UserStatusReader userStatusReader = new UserStatusReader(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel());
-            userStatusReader.populateUserInfoBean(userInfoBean, locale, userIdentity, chaiProvider);
-        } else {
-            userInfoBean = null;
-        }
-        UserDataReader userDataReader = new LdapUserDataReader(userIdentity, chaiUser);
-        return new MacroMachine(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userInfoBean, null, userDataReader);
-    }
-
-    private static void checkIfUserIdentityViewable(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws  PwmUnrecoverableException, PwmOperationalException
-    {
-        final String filterSetting = getSearchFilter(pwmRequest.getConfig());
-        String filterString = filterSetting.replace(PwmConstants.VALUE_REPLACEMENT_USERNAME, "*");
-        while (filterString.contains("**")) {
-            filterString = filterString.replace("**", "*");
-        }
-
-        final boolean match = LdapPermissionTester.testQueryMatch(pwmRequest.getPwmApplication(), pwmRequest.getSessionLabel(), userIdentity, filterString);
-        if (!match) {
-            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "requested userDN is not available within configured search filter"));
-        }
-    }
-
-    private static PhotoDataBean readPhotoDataFromLdap(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity
-    )
-            throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException
-    {
-        final String attribute = pwmRequest.getConfig().readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
-        if (attribute == null || attribute.isEmpty()) {
-            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "ldap photo attribute is not configured"));
-        }
-
-        byte[] photoData;
-        String mimeType;
-        try {
-            final ChaiUser chaiUser = getChaiUser(pwmRequest, userIdentity);
-            final byte[][] photoAttributeData = chaiUser.readMultiByteAttribute(attribute);
-            if (photoAttributeData == null || photoAttributeData.length == 0 || photoAttributeData[0].length == 0) {
-                throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_SERVICE_NOT_AVAILABLE, "user has no photo data stored in LDAP attribute"));
-            }
-            photoData = photoAttributeData[0];
-            mimeType = URLConnection.guessContentTypeFromStream(new ByteArrayInputStream(photoData));
-        } catch (IOException | ChaiOperationException e) {
-            throw new PwmOperationalException(new ErrorInformation(PwmError.ERROR_UNKNOWN, "error reading user photo ldap attribute: " + e.getMessage()));
-        }
-        return new PhotoDataBean(mimeType, photoData);
-    }
-
-    private static String getSearchFilter(final Configuration configuration) {
-        final String configuredFilter = configuration.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 StringBuilder filter = new StringBuilder();
-        filter.append("(&"); //open AND clause for objectclasses and attributes
-        for (final String objectClass : defaultObjectClasses) {
-            filter.append("(objectClass=").append(objectClass).append(")");
-        }
-        filter.append("(|"); // open OR clause for attributes
-        for (final String searchAttribute : searchAttributes) {
-            filter.append("(").append(searchAttribute).append("=*").append(PwmConstants.VALUE_REPLACEMENT_USERNAME).append("*)");
-        }
-        filter.append(")"); // close OR clause
-        filter.append(")"); // close AND clause
-        return filter.toString();
-    }
-
-    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 static OrgChartReferenceBean makeOrgChartReferenceForIdentity(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity,
-            final String nextNodeAttribute
-    )
-            throws PwmUnrecoverableException
-    {
-        final OrgChartReferenceBean orgChartReferenceBean = new OrgChartReferenceBean();
-        orgChartReferenceBean.setUserKey(userIdentity.toObfuscatedKey(pwmRequest.getPwmApplication()));
-        orgChartReferenceBean.setPhotoURL(figurePhotoURL(pwmRequest, userIdentity));
-
-        final List<String> displayLabels = figureDisplaynames(pwmRequest, userIdentity);
-        orgChartReferenceBean.setDisplayNames(displayLabels);
-
-        orgChartReferenceBean.setHasMoreNodes(false);
-        try {
-            final UserDataReader userDataReader = new LdapUserDataReader(userIdentity, getChaiUser(pwmRequest, userIdentity));
-            final String nextNodeValue = userDataReader.readStringAttribute(nextNodeAttribute);
-            if (nextNodeValue != null && !nextNodeValue.isEmpty()) {
-                orgChartReferenceBean.setHasMoreNodes(true);
-            }
-        } catch (ChaiException e) {
-            LOGGER.debug(pwmRequest, "error reading nextNodeAttribute during orgChratReference construction: " + e.getMessage());
-        }
-
-        return orgChartReferenceBean;
-    }
-
-    private static List<UserIdentity> readUserDNAttributeValues(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity,
-            final String attributeName
-    )
-            throws PwmUnrecoverableException
-    {
-
-        final List<UserIdentity> returnObj = new ArrayList<>();
-
-        final int MAX_VALUES = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_VALUE_MAXCOUNT));
-        final ChaiUser chaiUser = getChaiUser(pwmRequest, userIdentity);
-        final Set<String> ldapValues;
-        try {
-            ldapValues = chaiUser.readMultiStringAttribute(attributeName);
-        } catch (ChaiOperationException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" +  e.getMessage()));
-        } catch (ChaiUnavailableException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage()));
-        }
-
-
-        final boolean checkUserDNValues = Boolean.parseBoolean(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_MAX_VALUE_VERIFYUSERDN));
-        for (final String userDN : ldapValues) {
-            final UserIdentity loopIdentity = new UserIdentity(userDN, userIdentity.getLdapProfileID());
-            if (returnObj.size() < MAX_VALUES) {
-                try {
-                    if (checkUserDNValues) {
-                        checkIfUserIdentityViewable(pwmRequest, loopIdentity);
-                    }
-                    returnObj.add(loopIdentity);
-                } catch (PwmOperationalException e) {
-                    LOGGER.debug(pwmRequest, "discarding userDN " + userDN + " from attribute " + attributeName + " because it does not match search filter");
-                }
-            } else {
-                LOGGER.trace(pwmRequest, "discarding userDN " + userDN + " from attribute " + attributeName + " because maximum value count has been reached");
-            }
-
-        }
-        return returnObj;
-    }
-
-    private static List<String> readUserMultiAttributeValues(
-            final PwmRequest pwmRequest,
-            final UserIdentity userIdentity,
-            final String attributeName
-    )
-            throws PwmUnrecoverableException
-    {
-
-        final List<UserIdentity> returnObj = new ArrayList<>();
-
-        final int MAX_VALUES = Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.PEOPLESEARCH_VALUE_MAXCOUNT));
-        final ChaiUser chaiUser = getChaiUser(pwmRequest, userIdentity);
-        try {
-            final Set<String> ldapValues = chaiUser.readMultiStringAttribute(attributeName);
-            return ldapValues != null ? new ArrayList<>(ldapValues) : Collections.<String>emptyList();
-        } catch (ChaiOperationException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, "error reading attribute value '" + attributeName + "', error:" +  e.getMessage()));
-        } catch (ChaiUnavailableException e) {
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE, e.getMessage()));
-        }
-
-    }
-
-    private CacheKey makeCacheKey(
-            final PwmRequest pwmRequest,
-            final String operationIdentifer,
-            final String dataIdentifer
-    )
-            throws PwmUnrecoverableException
-    {
-        final UserIdentity userIdentity;
-        if (pwmRequest.isAuthenticated() && !useProxy(pwmRequest)) {
-            userIdentity = pwmRequest.getUserInfoIfLoggedIn();
-        } else {
-            userIdentity = null;
-        }
-        final String keyString = operationIdentifer + "|" + pwmRequest.getPwmApplication().getSecureService().hash(dataIdentifer);
-        return CacheKey.makeCacheKey(
-                this.getClass(),
-                userIdentity,
-                keyString);
-    }
-
-    private static class PeopleSearchConfiguration {
-        private final Configuration configuration;
-
-        public PeopleSearchConfiguration(Configuration configuration) {
-            this.configuration = configuration;
-        }
-
-        public String getPhotoAttribute() {
-            return configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_ATTRIBUTE);
-        }
-
-        public String getPhotoUrlOverride() {
-            return configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_PHOTO_URL_OVERRIDE);
-        }
-
-        public boolean isPhotosEnabled() {
-            return (getPhotoAttribute() != null
-                    && !getPhotoAttribute().isEmpty())
-                    ||
-                    (getPhotoUrlOverride() != null
-                            && !getPhotoUrlOverride().isEmpty());
-        }
-
-        public boolean orgChartIsEnabled() {
-            final String orgChartParentAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_PARENT_ATTRIBUTE);
-            final String orgChartChildAttr = configuration.readSettingAsString(PwmSetting.PEOPLE_SEARCH_ORGCHART_CHILD_ATTRIBUTE);
-            return orgChartParentAttr != null && !orgChartParentAttr.isEmpty() && orgChartChildAttr != null && !orgChartChildAttr.isEmpty();
-        }
-    }
 }

+ 0 - 18
src/main/java/password/pwm/http/servlet/peoplesearch/UserDetailBean.java

@@ -31,8 +31,6 @@ class UserDetailBean implements Serializable {
     private String userKey;
     private Map<String, AttributeDetailBean> detail;
     private String photoURL;
-    private boolean hasOrgChart;
-    private String orgChartParentKey;
 
     public List<String> getDisplayNames() {
         return displayNames;
@@ -65,20 +63,4 @@ class UserDetailBean implements Serializable {
     public void setPhotoURL(String photoURL) {
         this.photoURL = photoURL;
     }
-
-    public boolean isHasOrgChart() {
-        return hasOrgChart;
-    }
-
-    public void setHasOrgChart(boolean hasOrgChart) {
-        this.hasOrgChart = hasOrgChart;
-    }
-
-    public String getOrgChartParentKey() {
-        return orgChartParentKey;
-    }
-
-    public void setOrgChartParentKey(String orgChartParentKey) {
-        this.orgChartParentKey = orgChartParentKey;
-    }
 }

+ 17 - 19
src/main/java/password/pwm/util/LDAPPermissionCalculator.java

@@ -338,27 +338,25 @@ public class LDAPPermissionCalculator implements Serializable {
 
         final PwmSettingTemplateSet templateSet =  storedConfiguration.getTemplateSet();
 
-        { //
+        { // edir specific attributes
 
             if (!Collections.disjoint(templateSet.getTemplates(), EDIR_INTERESTED_TEMPLATES)) {
-                if (configuration.readSettingAsBoolean(PwmSetting.FORGOTTEN_PASSWORD_ENABLE)) {
-                    final String[] ldapAttributes = new String[] {
-                            ChaiConstant.ATTR_LDAP_LOCKED_BY_INTRUDER,
-                            ChaiConstant.ATTR_LDAP_LOGIN_INTRUDER_ATTEMPTS,
-                            ChaiConstant.ATTR_LDAP_LOGIN_INTRUDER_RESET_TIME,
-                            ChaiConstant.ATTR_LDAP_LOGIN_GRACE_LIMIT,
-                            ChaiConstant.ATTR_LDAP_LOGIN_GRACE_REMAINING,
-                    };
-
-                    for (final String ldapAttribute : ldapAttributes) {
-                        permissionRecords.add(new PermissionRecord(
-                                ldapAttribute,
-                                PwmSetting.FORGOTTEN_PASSWORD_ENABLE,
-                                null,
-                                LDAPPermissionInfo.Access.write,
-                                LDAPPermissionInfo.Actor.proxy
-                        ));
-                    }
+                final Map<String, LDAPPermissionInfo.Access> ldapAttributes = new LinkedHashMap<>();
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_LOCKED_BY_INTRUDER,LDAPPermissionInfo.Access.write);
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_LOGIN_INTRUDER_ATTEMPTS,LDAPPermissionInfo.Access.write);
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_LOGIN_INTRUDER_RESET_TIME,LDAPPermissionInfo.Access.write);
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_LOGIN_GRACE_LIMIT,LDAPPermissionInfo.Access.write);
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_LOGIN_GRACE_REMAINING,LDAPPermissionInfo.Access.write);
+                ldapAttributes.put(ChaiConstant.ATTR_LDAP_PASSWORD_EXPIRE_TIME,LDAPPermissionInfo.Access.read);
+
+                for (final String ldapAttribute : ldapAttributes.keySet()) {
+                    permissionRecords.add(new PermissionRecord(
+                            ldapAttribute,
+                            null,
+                            null,
+                            ldapAttributes.get(ldapAttribute),
+                            LDAPPermissionInfo.Actor.proxy
+                    ));
                 }
             }
         }