Browse Source

LdapBrowser feature

jrivard 10 năm trước cách đây
mục cha
commit
9ae094bc91
34 tập tin đã thay đổi với 1269 bổ sung465 xóa
  1. 1 0
      pwm/servlet/src/password/pwm/AppProperty.properties
  2. 43 23
      pwm/servlet/src/password/pwm/PwmApplication.java
  3. 1 1
      pwm/servlet/src/password/pwm/PwmConstants.properties
  4. 4 4
      pwm/servlet/src/password/pwm/config/PwmSetting.java
  5. 21 0
      pwm/servlet/src/password/pwm/config/PwmSetting.xml
  6. 1 54
      pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java
  7. 14 14
      pwm/servlet/src/password/pwm/health/CertificateChecker.java
  8. 4 0
      pwm/servlet/src/password/pwm/health/ConfigurationChecker.java
  9. 1 0
      pwm/servlet/src/password/pwm/health/HealthMessage.java
  10. 1 1
      pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java
  11. 0 1
      pwm/servlet/src/password/pwm/http/ContextManager.java
  12. 4 8
      pwm/servlet/src/password/pwm/http/bean/ConfigManagerBean.java
  13. 25 0
      pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java
  14. 103 40
      pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java
  15. 1 1
      pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java
  16. 3 2
      pwm/servlet/src/password/pwm/i18n/Health.properties
  17. 270 0
      pwm/servlet/src/password/pwm/ldap/LdapBrowser.java
  18. 1 1
      pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java
  19. 1 1
      pwm/servlet/src/password/pwm/util/cli/MainClass.java
  20. 1 1
      pwm/servlet/web/WEB-INF/jsp/configeditor.jsp
  21. 0 1
      pwm/servlet/web/WEB-INF/jsp/configguide-app.jsp
  22. 3 3
      pwm/servlet/web/WEB-INF/jsp/configguide-end.jsp
  23. 162 0
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap_admin.jsp
  24. 190 0
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap_cert.jsp
  25. 18 4
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap_context.jsp
  26. 0 27
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap_server.jsp
  27. 12 4
      pwm/servlet/web/WEB-INF/jsp/configguide-ldap_testuser.jsp
  28. 0 2
      pwm/servlet/web/WEB-INF/jsp/configguide-password.jsp
  29. 11 0
      pwm/servlet/web/public/resources/configStyle.css
  30. 63 265
      pwm/servlet/web/public/resources/js/configeditor-settings.js
  31. 4 0
      pwm/servlet/web/public/resources/js/configeditor.js
  32. 7 7
      pwm/servlet/web/public/resources/js/configguide.js
  33. 298 0
      pwm/servlet/web/public/resources/js/uilibrary.js
  34. 1 0
      pwm/servlet/web/public/resources/style.css

+ 1 - 0
pwm/servlet/src/password/pwm/AppProperty.properties

@@ -120,6 +120,7 @@ ldap.search.timeoutMS=30000
 ldap.password.replicaCheck.initialDelayMS=1000
 ldap.password.replicaCheck.cycleDelayMS=7000
 ldap.guid.pattern=@UUID@
+ldap.browser.maxEntries=5000
 localdb.compression.enabled=true
 localdb.decompression.enabled=true
 localdb.compression.minSize=1024

+ 43 - 23
pwm/servlet/src/password/pwm/PwmApplication.java

@@ -129,6 +129,7 @@ public class PwmApplication {
     private final File applicationPath;
     private final File webInfPath;
     private final File configurationFile;
+    private final PwmEnvironment pwmEnvironment;
 
     private MODE applicationMode;
 
@@ -159,6 +160,7 @@ public class PwmApplication {
     private PwmApplication(final PwmEnvironment pwmEnvironment)
             throws PwmUnrecoverableException
     {
+        this.pwmEnvironment = pwmEnvironment;
         verifyIfApplicationPathIsSetProperly(pwmEnvironment);
 
         this.configuration = pwmEnvironment.config;
@@ -168,20 +170,20 @@ public class PwmApplication {
         this.webInfPath = pwmEnvironment.webInfPath;
 
         try {
-            initialize(pwmEnvironment.initLogging);
+            initialize();
         } catch (PwmUnrecoverableException e) {
             LOGGER.fatal(e.getMessage());
             throw e;
         }
     }
 
-    private void initialize(final boolean initLogging)
+    private void initialize()
             throws PwmUnrecoverableException
     {
         final Date startTime = new Date();
 
         // initialize log4j
-        if (initLogging) {
+        if (!pwmEnvironment.internalRuntimeInstance) {
             final String log4jFileName = configuration.readSettingAsString(PwmSetting.EVENTS_JAVA_LOG4JCONFIG_FILE);
             final File log4jFile = Helper.figureFilepath(log4jFileName, applicationPath);
             final String consoleLevel, fileLevel;
@@ -219,7 +221,10 @@ public class PwmApplication {
                         + ", configurationFile=" + (configurationFile == null ? "null" : configurationFile.getAbsolutePath())
         );
 
-        this.localDB = Initializer.initializeLocalDB(this);
+        if (!pwmEnvironment.internalRuntimeInstance) {
+            this.localDB = Initializer.initializeLocalDB(this);
+        }
+
         this.localDBLogger = PwmLogManager.initializeLocalDBLogger(this);
 
         // log the loaded configuration
@@ -235,20 +240,22 @@ public class PwmApplication {
 
         initServices();
 
-        final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
-        LOGGER.info(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")");
-        StatisticsManager.incrementStat(this,Statistic.PWM_STARTUPS);
-        LOGGER.debug("buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE);
+        if (!pwmEnvironment.internalRuntimeInstance) {
+            final TimeDuration totalTime = TimeDuration.fromCurrent(startTime);
+            LOGGER.info(PwmConstants.PWM_APP_NAME + " " + PwmConstants.SERVLET_VERSION + " open for bidness! (" + totalTime.asCompactString() + ")");
+            StatisticsManager.incrementStat(this,Statistic.PWM_STARTUPS);
+            LOGGER.debug("buildTime=" + PwmConstants.BUILD_TIME + ", javaLocale=" + Locale.getDefault() + ", DefaultLocale=" + PwmConstants.DEFAULT_LOCALE);
 
-        final Thread postInitThread = new Thread(){
-            @Override
-            public void run() {
-                postInitTasks();
-            }
-        };
-        postInitThread.setDaemon(true);
-        postInitThread.setName(Helper.makeThreadName(this, PwmApplication.class));
-        postInitThread.start();
+            final Thread postInitThread = new Thread() {
+                @Override
+                public void run() {
+                    postInitTasks();
+                }
+            };
+            postInitThread.setDaemon(true);
+            postInitThread.setName(Helper.makeThreadName(this, PwmApplication.class));
+            postInitThread.start();
+        }
     }
 
     private void initServices()
@@ -799,7 +806,7 @@ public class PwmApplication {
 
         private Configuration config;
         private File applicationPath;
-        private boolean initLogging;
+        private boolean internalRuntimeInstance;
         private File configurationFile;
         private File webInfPath;
         private ApplicationPathType applicationPathType = ApplicationPathType.derived;
@@ -819,11 +826,6 @@ public class PwmApplication {
             return this;
         }
 
-        public PwmEnvironment setInitLogging(boolean initLogging) {
-            this.initLogging = initLogging;
-            return this;
-        }
-
         public PwmEnvironment setConfigurationFile(File configurationFile) {
             this.configurationFile = configurationFile;
             return this;
@@ -839,12 +841,30 @@ public class PwmApplication {
             return this;
         }
 
+        public PwmEnvironment setInternalRuntimeInstance(boolean internalRuntimeInstance) {
+            this.internalRuntimeInstance = internalRuntimeInstance;
+            return this;
+        }
+
         public PwmApplication createPwmApplication()
                 throws PwmUnrecoverableException
         {
             return new PwmApplication(this);
         }
     }
+
+    public PwmApplication makePwmRuntimeInstance(
+            final Configuration configuration
+    ) throws PwmUnrecoverableException {
+        return new PwmApplication.PwmEnvironment(configuration,this.getApplicationPath())
+                .setApplicationMode(PwmApplication.MODE.NEW)
+                .setInternalRuntimeInstance(true)
+                .setConfigurationFile(null)
+                .setWebInfPath(this.getWebInfPath())
+                .createPwmApplication();
+    }
+
 }
 
 
+

+ 1 - 1
pwm/servlet/src/password/pwm/PwmConstants.properties

@@ -44,7 +44,7 @@ paramName.token=token
 defaultConfigFilename=PwmConfiguration.xml
 pwmDBLoggerMaxQueueSize=50000
 pwmDBLoggerMaxDirtyBufferMS=500
-enableEulaDisplay=true
+enableEulaDisplay=false
 databaseAccessor.keyLength=128
 missingVersionString=[Version Missing]
 applicationPathInfoFile=applicationPath.properties

+ 4 - 4
pwm/servlet/src/password/pwm/config/PwmSetting.java

@@ -182,10 +182,6 @@ public enum PwmSetting {
             "ldap.guidAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.LDAP_PROFILE),
     LDAP_LOGIN_CONTEXTS(
             "ldap.selectableContexts", PwmSettingSyntax.STRING_ARRAY, PwmSettingCategory.LDAP_PROFILE),
-    LDAP_PROFILE_DISPLAY_NAME(
-            "ldap.profile.displayName", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.LDAP_PROFILE),
-    LDAP_PROFILE_ENABLED(
-            "ldap.profile.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.LDAP_PROFILE),
     LDAP_NAMING_ATTRIBUTE(
             "ldap.namingAttribute", PwmSettingSyntax.STRING, PwmSettingCategory.LDAP_PROFILE),
     PASSWORD_LAST_UPDATE_ATTRIBUTE(
@@ -196,6 +192,10 @@ public enum PwmSetting {
             "ldap.group.label.attribute", PwmSettingSyntax.STRING, PwmSettingCategory.LDAP_PROFILE),
     LDAP_SEARCH_TIMEOUT(
             "ldap.search.timeoutSeconds", PwmSettingSyntax.DURATION, PwmSettingCategory.LDAP_PROFILE),
+    LDAP_PROFILE_DISPLAY_NAME(
+            "ldap.profile.displayName", PwmSettingSyntax.LOCALIZED_STRING, PwmSettingCategory.LDAP_PROFILE),
+    LDAP_PROFILE_ENABLED(
+            "ldap.profile.enabled", PwmSettingSyntax.BOOLEAN, PwmSettingCategory.LDAP_PROFILE),
 
 
     // ldap global settings

+ 21 - 0
pwm/servlet/src/password/pwm/config/PwmSetting.xml

@@ -387,6 +387,9 @@
         <default template="AD">
             <value><![CDATA[cn=administrator,cn=users,dc=site,dc=example,dc=net]]></value>
         </default>
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="ldap.proxy.password" level="0">
     </setting>
@@ -397,6 +400,9 @@
         <default template="AD">
             <value><![CDATA[cn=users,dc=site,dc=example,dc=net]]></value>
         </default>
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="ldap.selectableContexts" level="2">
         <regex>^.+:::.+$</regex>
@@ -406,6 +412,9 @@
         <default>
             <value />
         </default>
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="pwmAdmin.queryMatch" level="0" required="true">
         <default syntaxVersion="2">
@@ -2053,6 +2062,9 @@
         <default>
             <value><![CDATA[ou=users,o=example]]></value>
         </default>
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="display.newuser.agreement" level="1">
         <default>
@@ -2116,6 +2128,9 @@
         <default>
             <value><![CDATA[ou=guests,o=example]]></value>
         </default>
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="guest.adminGroup" level="1" required="true">
         <default syntaxVersion="2">
@@ -2342,6 +2357,9 @@
     </setting>
     <setting key="peopleSearch.searchBase" level="2">
         <default />
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="peopleSearch.result.form" level="1" required="true">
         <default>
@@ -2618,6 +2636,9 @@
     </setting>
     <setting key="helpdesk.searchBase" level="1">
         <default />
+        <options>
+            <option value="ldapDNsyntax">true</option>
+        </options>
     </setting>
     <setting key="helpdesk.result.limit" level="2" required="true">
         <default>

+ 1 - 54
pwm/servlet/src/password/pwm/config/function/UserMatchViewerFunction.java

@@ -35,7 +35,6 @@ 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.LdapPermissionTester;
 import password.pwm.util.logging.PwmLogger;
 
@@ -77,7 +76,7 @@ public class UserMatchViewerFunction implements SettingUIFunction {
         final Configuration config = new Configuration(storedConfiguration);
         final PwmApplication tempApplication = new PwmApplication.PwmEnvironment(config,pwmApplication.getApplicationPath())
                 .setApplicationMode(PwmApplication.MODE.NEW)
-                .setInitLogging(false)
+                .setInternalRuntimeInstance(true)
                 .setConfigurationFile(null)
                 .setWebInfPath(pwmApplication.getWebInfPath())
                 .createPwmApplication();
@@ -96,58 +95,6 @@ public class UserMatchViewerFunction implements SettingUIFunction {
         return LdapPermissionTester.discoverMatchingUsers(tempApplication, maxResultSize, permissions).keySet();
     }
 
-    public String convertResultsToHtmlTable(
-            final PwmApplication pwmApplication,
-            final Locale userLocale,
-            final Map<String,List<String>> sortedMap,
-            final int maxResultSize
-    )
-    {
-        final StringBuilder output = new StringBuilder();
-        final Configuration config = pwmApplication.getConfig();
-
-        if (sortedMap.isEmpty()) {
-            output.append(Display.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE,Display.Display_SearchResultsNone,pwmApplication.getConfig()));
-        } else {
-            output.append("<table>");
-            output.append("<tr><td class=\"title\">");
-            output.append(Display.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE,Display.Field_LdapProfile,pwmApplication.getConfig()));
-            output.append("</td><td class=\"title\">");
-            output.append(Display.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE,Display.Field_UserDN,pwmApplication.getConfig()));
-            output.append("</td></tr>");
-
-            for (final String loopProfile : sortedMap.keySet()) {
-                final String profileName = config.getLdapProfiles().get(loopProfile).getDisplayName(userLocale);
-                for (final String loopDN : sortedMap.get(loopProfile)) {
-                    output.append("<tr><td>");
-                    output.append(profileName);
-                    output.append("</td><td>");
-                    output.append(loopDN);
-                    output.append("</td></tr>");
-                }
-            }
-            output.append("</table>");
-            if (sortedMap.size() >= maxResultSize) {
-                output.append("<br/>");
-                output.append(Display.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE,Display.Display_SearchResultsExceeded,pwmApplication.getConfig()));
-            }
-        }
-
-        return output.toString();
-    }
-
-    private Map<String,List<String>> sortResults(final Map<UserIdentity, Map<String, String>> results) {
-        final Map<String,List<String>> sortedMap = new TreeMap<>();
-
-        for (final UserIdentity userIdentity : results.keySet()) {
-            if (!sortedMap.containsKey(userIdentity.getLdapProfileID())) {
-                sortedMap.put(userIdentity.getLdapProfileID(), new ArrayList<String>());
-            }
-            sortedMap.get(userIdentity.getLdapProfileID()).add(userIdentity.getUserDN());
-        }
-
-        return sortedMap;
-    }
 
     private void testIfLdapDNIsValid(final PwmApplication pwmApplication, final String baseDN, final String profileID)
             throws PwmOperationalException, PwmUnrecoverableException {

+ 14 - 14
pwm/servlet/src/password/pwm/health/CertificateChecker.java

@@ -53,40 +53,40 @@ public class CertificateChecker implements HealthChecker {
             if (setting.getSyntax() == PwmSettingSyntax.X509CERT) {
                 if (setting != PwmSetting.LDAP_SERVER_CERTS) {
                     final X509Certificate[] certs = configuration.readSettingAsCertificate(setting);
-                    returnList.addAll(doHealthCheck(configuration,setting,certs));
+                    returnList.addAll(doHealthCheck(configuration,setting,null,certs));
                 }
             }
         }
         for (final LdapProfile ldapProfile : configuration.getLdapProfiles().values()) {
            final X509Certificate[] certificates = configuration.getLdapProfiles().get(ldapProfile.getIdentifier()).readSettingAsCertificate(PwmSetting.LDAP_SERVER_CERTS);
-            returnList.addAll(doHealthCheck(configuration,PwmSetting.LDAP_SERVER_CERTS,certificates));
+            returnList.addAll(doHealthCheck(configuration,PwmSetting.LDAP_SERVER_CERTS,ldapProfile.getIdentifier(),certificates));
         }
         return Collections.unmodifiableList(returnList);
     }
 
-    private static List<HealthRecord> doHealthCheck(Configuration configuration, PwmSetting setting, X509Certificate[] certificates) {
+    private static List<HealthRecord> doHealthCheck(Configuration configuration, PwmSetting setting, final String profileID, X509Certificate[] certificates) {
         final long warnDurationMs = 1000 * Long.parseLong(configuration.readAppProperty(AppProperty.HEALTH_CERTIFICATE_WARN_SECONDS));
 
         if (certificates != null) {
             final List<HealthRecord> returnList = new ArrayList<>();
             for (final X509Certificate certificate : certificates) {
-                returnList.addAll(doHealthCheck(certificate, warnDurationMs));
+                try {
+                    checkCertificate(certificate, warnDurationMs);
+                    return Collections.emptyList();
+                } catch (PwmOperationalException e) {
+                    final String errorDetail = e.getErrorInformation().getDetailedErrorMsg();
+                    final HealthRecord record = HealthRecord.forMessage(HealthMessage.Config_Certificate,
+                            setting.toMenuLocationDebug(profileID,PwmConstants.DEFAULT_LOCALE),
+                            errorDetail
+                    );
+                    return Collections.singletonList(record);
+                }
             }
             return returnList;
         }
         return Collections.emptyList();
     }
 
-    private static List<HealthRecord> doHealthCheck(X509Certificate certificate, final long warnDurationMs) {
-        try {
-            checkCertificate(certificate, warnDurationMs);
-            return Collections.emptyList();
-        } catch (PwmOperationalException e) {
-            final HealthRecord record = new HealthRecord(HealthStatus.WARN, HealthTopic.Configuration,e.getErrorInformation().toDebugStr());
-            return Collections.singletonList(record);
-        }
-    }
-
     public static void checkCertificate(final X509Certificate certificate, final long warnDurationMs)
             throws PwmOperationalException
     {

+ 4 - 0
pwm/servlet/src/password/pwm/health/ConfigurationChecker.java

@@ -252,6 +252,10 @@ public class ConfigurationChecker implements HealthChecker {
             records.add(HealthRecord.forMessage(HealthMessage.Config_UsingLocalDBResponseStorage, settingToOutputText(PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE)));
         }
 
+        if (config.getOtpSecretStorageLocations(PwmSetting.OTP_SECRET_WRITE_PREFERENCE).contains(DataStorageMethod.LOCALDB)) {
+            records.add(HealthRecord.forMessage(HealthMessage.Config_UsingLocalDBResponseStorage, settingToOutputText(PwmSetting.OTP_SECRET_WRITE_PREFERENCE)));
+        }
+
         return records;
     }
 

+ 1 - 0
pwm/servlet/src/password/pwm/health/HealthMessage.java

@@ -61,6 +61,7 @@ public enum HealthMessage {
     Config_PasswordPolicyProblem            (HealthStatus.CONFIG,   HealthTopic.Configuration),
     Config_UserPermissionValidity           (HealthStatus.CONFIG,   HealthTopic.Configuration),
     Config_NoRecoveryEnabled                (HealthStatus.CAUTION,  HealthTopic.Configuration),
+    Config_Certificate                      (HealthStatus.WARN,     HealthTopic.Configuration),
     LDAP_VendorsNotSame                     (HealthStatus.CONFIG,   HealthTopic.LDAP),
     LDAP_OK                                 (HealthStatus.GOOD,     HealthTopic.LDAP),
     LDAP_RecentlyUnreachable                (HealthStatus.CAUTION,  HealthTopic.LDAP),

+ 1 - 1
pwm/servlet/src/password/pwm/health/LDAPStatusChecker.java

@@ -674,7 +674,7 @@ public class LDAPStatusChecker implements HealthChecker {
     {
         final PwmApplication tempApplication = new PwmApplication.PwmEnvironment(config, pwmApplication.getApplicationPath())
                 .setApplicationMode(PwmApplication.MODE.NEW)
-                .setInitLogging(false)
+                .setInternalRuntimeInstance(true)
                 .setWebInfPath(pwmApplication.getWebInfPath())
                 .createPwmApplication();
 

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

@@ -188,7 +188,6 @@ public class ContextManager implements Serializable {
         try {
             pwmApplication = new PwmApplication.PwmEnvironment(configuration, applicationPath)
                     .setApplicationMode(mode)
-                    .setInitLogging(true)
                     .setConfigurationFile(configurationFile)
                     .setWebInfPath(webInfPath)
                     .setApplicationPathType(applicationPathType).createPwmApplication();

+ 4 - 8
pwm/servlet/src/password/pwm/http/bean/ConfigManagerBean.java

@@ -25,7 +25,7 @@ package password.pwm.http.bean;
 import password.pwm.config.StoredConfiguration;
 
 public class ConfigManagerBean implements PwmSessionBean {
-    private StoredConfiguration configuration;
+    private StoredConfiguration storedConfiguration;
     private boolean passwordVerified;
     private boolean configUnlockedWarningShown;
 
@@ -35,11 +35,11 @@ public class ConfigManagerBean implements PwmSessionBean {
     }
 
     public StoredConfiguration getStoredConfiguration() {
-        return configuration;
+        return storedConfiguration;
     }
 
-    public void setConfiguration(final StoredConfiguration configuration) {
-        this.configuration = configuration;
+    public void setConfiguration(final StoredConfiguration storedConfiguration) {
+        this.storedConfiguration = storedConfiguration;
     }
 
     public boolean isPasswordVerified() {
@@ -60,10 +60,6 @@ public class ConfigManagerBean implements PwmSessionBean {
         this.prePasswordEntryUrl = prePasswordEntryUrl;
     }
 
-    public StoredConfiguration getConfiguration() {
-        return configuration;
-    }
-
     public boolean isConfigUnlockedWarningShown() {
         return configUnlockedWarningShown;
     }

+ 25 - 0
pwm/servlet/src/password/pwm/http/servlet/ConfigEditorServlet.java

@@ -43,6 +43,7 @@ import password.pwm.i18n.Config;
 import password.pwm.i18n.LocaleHelper;
 import password.pwm.i18n.Message;
 import password.pwm.i18n.PwmLocaleBundle;
+import password.pwm.ldap.LdapBrowser;
 import password.pwm.util.JsonUtil;
 import password.pwm.util.StringUtil;
 import password.pwm.util.TimeDuration;
@@ -88,6 +89,7 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
         menuTreeData(HttpMethod.POST),
         settingData(HttpMethod.GET),
         testMacro(HttpMethod.POST),
+        browseLdap(HttpMethod.POST),
 
         ;
 
@@ -240,6 +242,10 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
                 case testMacro:
                     restTestMacro(pwmRequest);
                     return;
+
+                case browseLdap:
+                    restBrowseLdap(pwmRequest);
+                    return;
             }
         }
 
@@ -1040,4 +1046,23 @@ public class ConfigEditorServlet extends AbstractPwmServlet {
             pwmRequest.respondWithError(e.getErrorInformation());
         }
     }
+
+    private void restBrowseLdap(final PwmRequest pwmRequest) throws IOException, ServletException, PwmUnrecoverableException {
+        final Date startTime = new Date();
+        final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap(true);
+        final String profile = inputMap.get("profile");
+        final String dn = inputMap.containsKey("dn") ? inputMap.get("dn") : "";
+
+        final LdapBrowser ldapBrowser = new LdapBrowser(pwmRequest.getPwmSession().getConfigManagerBean().getStoredConfiguration());
+        final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse(profile, dn);
+        ldapBrowser.close();
+
+        LOGGER.trace(pwmRequest, "performed ldapBrowse operation in "
+                + TimeDuration.fromCurrent(startTime).asCompactString()
+                + ", result=" + JsonUtil.serialize(result));
+
+        pwmRequest.outputJsonResult(new RestResultBean(result));
+    }
+
+
 }

+ 103 - 40
pwm/servlet/src/password/pwm/http/servlet/ConfigGuideServlet.java

@@ -31,6 +31,7 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
+import password.pwm.bean.UserIdentity;
 import password.pwm.config.*;
 import password.pwm.config.function.UserMatchViewerFunction;
 import password.pwm.config.profile.LdapProfile;
@@ -42,12 +43,10 @@ import password.pwm.http.HttpMethod;
 import password.pwm.http.PwmRequest;
 import password.pwm.http.PwmSession;
 import password.pwm.http.bean.ConfigGuideBean;
+import password.pwm.ldap.LdapBrowser;
 import password.pwm.ldap.schema.SchemaManager;
 import password.pwm.ldap.schema.SchemaOperationResult;
-import password.pwm.util.Helper;
-import password.pwm.util.PasswordData;
-import password.pwm.util.ServletHelper;
-import password.pwm.util.X509Utils;
+import password.pwm.util.*;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.ws.server.RestResultBean;
 import password.pwm.ws.server.rest.bean.HealthData;
@@ -59,7 +58,7 @@ import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Serializable;
-import java.net.URI;
+import java.net.*;
 import java.security.cert.X509Certificate;
 import java.util.*;
 
@@ -67,7 +66,8 @@ import java.util.*;
 @WebServlet(
         name = "ConfigGuideServlet",
         urlPatterns = {
-            PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigGuide"
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/config-guide",
+                PwmConstants.URL_PREFIX_PRIVATE + "/config/ConfigGuide"
         }
 )
 public class ConfigGuideServlet extends AbstractPwmServlet {
@@ -84,9 +84,9 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
     public static final String PARAM_LDAP_ADMIN_DN = "ldap-user-dn";
     public static final String PARAM_LDAP_ADMIN_PW = "ldap-user-pw";
 
-    public static final String PARAM_LDAP2_CONTEXT = "ldap2-context";
-    public static final String PARAM_LDAP2_TEST_USER = "ldap2-testUser";
-    public static final String PARAM_LDAP2_ADMIN_GROUP = "ldap2-adminGroup";
+    public static final String PARAM_LDAP_CONTEXT = "ldap-context";
+    public static final String PARAM_LDAP_TEST_USER = "ldap-testUser";
+    public static final String PARAM_LDAP_ADMIN_GROUP = "ldap-adminGroup";
 
     public static final String PARAM_CR_STORAGE_PREF = "cr_storage-pref";
 
@@ -99,10 +99,11 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
     public enum STEP {
         START,
         TEMPLATE,
-        LDAP,
-        LDAPCERT,
-        LDAP2,
-        LDAP3,
+        LDAP_SERVER,
+        LDAP_CERT,
+        LDAP_ADMIN,
+        LDAP_CONTEXT,
+        LDAP_TESTUSER,
         CR_STORAGE,
         LDAP_SCHEMA,
         APP,
@@ -131,6 +132,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         uploadConfig(HttpMethod.POST),
         extendSchema(HttpMethod.POST),
         viewAdminMatches(HttpMethod.POST),
+        browseLdap(HttpMethod.POST),
         ;
 
         private final HttpMethod method;
@@ -171,12 +173,12 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             defaultLdapForm.put(PARAM_LDAP_ADMIN_DN, (String)PwmSetting.LDAP_PROXY_USER_DN.getDefaultValue(template).toNativeObject());
             defaultLdapForm.put(PARAM_LDAP_ADMIN_PW, "");
 
-            defaultLdapForm.put(PARAM_LDAP2_CONTEXT, ((List<String>)PwmSetting.LDAP_CONTEXTLESS_ROOT.getDefaultValue(template).toNativeObject()).get(0));
-            defaultLdapForm.put(PARAM_LDAP2_TEST_USER, (String)PwmSetting.LDAP_TEST_USER_DN.getDefaultValue(template).toNativeObject());
+            defaultLdapForm.put(PARAM_LDAP_CONTEXT, ((List<String>)PwmSetting.LDAP_CONTEXTLESS_ROOT.getDefaultValue(template).toNativeObject()).get(0));
+            defaultLdapForm.put(PARAM_LDAP_TEST_USER, (String)PwmSetting.LDAP_TEST_USER_DN.getDefaultValue(template).toNativeObject());
             {
                 List<UserPermission> userPermissions = (List<UserPermission>)PwmSetting.QUERY_MATCH_PWM_ADMIN.getDefaultValue(template).toNativeObject();
                 final String groupDN = userPermissions != null && userPermissions.size() > 0 ? userPermissions.get(0).getLdapBase() : "";
-                defaultLdapForm.put(PARAM_LDAP2_ADMIN_GROUP,groupDN);
+                defaultLdapForm.put(PARAM_LDAP_ADMIN_GROUP,groupDN);
             }
 
             defaultLdapForm.put(PARAM_CR_STORAGE_PREF, (String) PwmSetting.FORGOTTEN_PASSWORD_WRITE_PREFERENCE.getDefaultValue(template).toNativeObject());
@@ -198,7 +200,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         final PwmSession pwmSession = pwmRequest.getPwmSession();
         final PwmApplication pwmApplication = pwmRequest.getPwmApplication();
 
-        if (((ConfigGuideBean)pwmSession.getSessionBean(ConfigGuideBean.class)).getStep() == STEP.START) {
+        if ((pwmSession.getSessionBean(ConfigGuideBean.class)).getStep() == STEP.START) {
             pwmSession.clearSessionBeans();
             pwmSession.getSessionStateBean().setTheme(null);
         }
@@ -223,7 +225,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                 pwmRequest.getHttpServletRequest().getSession(),
                 Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.CONFIG_GUIDE_IDLE_TIMEOUT)));
 
-        if (configGuideBean.getStep() == STEP.LDAPCERT) {
+        if (configGuideBean.getStep() == STEP.LDAP_CERT) {
             final String ldapServerString = ((List<String>) configGuideBean.getStoredConfiguration().readSetting(PwmSetting.LDAP_SERVER_URLS, LDAP_PROFILE_KEY).toNativeObject()).get(0);
             try {
                 final URI ldapServerUri = new URI(ldapServerString);
@@ -270,6 +272,9 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                 case viewAdminMatches:
                     restViewAdminMatches(pwmRequest, configGuideBean);
                     return;
+
+                case browseLdap:
+                    restBrowseLdap(pwmRequest, configGuideBean);
             }
         }
 
@@ -349,14 +354,25 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         final Configuration tempConfiguration = new Configuration(configGuideBean.getStoredConfiguration());
         final PwmApplication tempApplication = new PwmApplication.PwmEnvironment(tempConfiguration, pwmRequest.getPwmApplication().getApplicationPath())
                 .setApplicationMode(PwmApplication.MODE.NEW)
-                .setInitLogging(false)
+                .setInternalRuntimeInstance(true)
                 .setWebInfPath(pwmRequest.getPwmApplication().getWebInfPath())
                 .createPwmApplication();
         final LDAPStatusChecker ldapStatusChecker = new LDAPStatusChecker();
         final List<HealthRecord> records = new ArrayList<>();
         final LdapProfile ldapProfile = tempConfiguration.getDefaultLdapProfile();
         switch (configGuideBean.getStep()) {
-            case LDAP: {
+            case LDAP_SERVER: {
+                try {
+                    checkLdapServer(configGuideBean);
+                    records.add(password.pwm.health.HealthRecord.forMessage(HealthMessage.LDAP_OK));
+                } catch (Exception e) {
+                    records.add(new HealthRecord(HealthStatus.WARN, HealthTopic.LDAP, "Can not connect to remote server: " + e.getMessage()));
+                }
+            }
+            break;
+
+
+            case LDAP_ADMIN: {
                 records.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, tempConfiguration, ldapProfile, false));
                 if (records.isEmpty()) {
                     records.add(password.pwm.health.HealthRecord.forMessage(HealthMessage.LDAP_OK));
@@ -364,15 +380,14 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             }
             break;
 
-            case LDAP2: {
+            case LDAP_CONTEXT: {
                 records.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, tempConfiguration, ldapProfile, true));
                 if (records.isEmpty()) {
                     records.add(new HealthRecord(HealthStatus.GOOD, HealthTopic.LDAP, "LDAP Contextless Login Root validated"));
                 }
-                    /*
                 try {
                     final UserMatchViewerFunction userMatchViewerFunction = new UserMatchViewerFunction();
-                    final Map<String, List<String>> results = userMatchViewerFunction.discoverMatchingUsers(
+                    final Collection<UserIdentity> results = userMatchViewerFunction.discoverMatchingUsers(
                             pwmRequest.getPwmApplication(),
                             2,
                             configGuideBean.getStoredConfiguration(),
@@ -390,12 +405,11 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                 } catch (Exception e) {
                     records.add(new HealthRecord(HealthStatus.WARN, HealthTopic.LDAP, "Error during admin group validation: " + e.getMessage()));
                 }
-                    */
             }
             break;
 
-            case LDAP3: {
-                final String testUserValue = configGuideBean.getFormData().get(PARAM_LDAP2_TEST_USER);
+            case LDAP_TESTUSER: {
+                final String testUserValue = configGuideBean.getFormData().get(PARAM_LDAP_TEST_USER);
                 if (testUserValue != null && !testUserValue.isEmpty()) {
                     records.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, tempConfiguration, ldapProfile, false));
                     records.addAll(ldapStatusChecker.doLdapTestUserCheck(tempConfiguration, ldapProfile, tempApplication));
@@ -435,6 +449,32 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
         }
     }
 
+    private void restBrowseLdap(
+            final PwmRequest pwmRequest,
+            final ConfigGuideBean configGuideBean
+    ) throws IOException, ServletException, PwmUnrecoverableException {
+        final StoredConfiguration storedConfiguration = StoredConfiguration.copy(configGuideBean.getStoredConfiguration());
+        if (configGuideBean.getStep() == STEP.LDAP_ADMIN) {
+            storedConfiguration.resetSetting(PwmSetting.LDAP_PROXY_USER_DN, LDAP_PROFILE_KEY, null);
+            storedConfiguration.resetSetting(PwmSetting.LDAP_PROXY_USER_PASSWORD, LDAP_PROFILE_KEY, null);
+        }
+
+        final Date startTime = new Date();
+        final Map<String, String> inputMap = pwmRequest.readBodyAsJsonStringMap(true);
+        final String profile = inputMap.get("profile");
+        final String dn = inputMap.containsKey("dn") ? inputMap.get("dn") : "";
+
+        final LdapBrowser ldapBrowser = new LdapBrowser(storedConfiguration);
+        final LdapBrowser.LdapBrowseResult result = ldapBrowser.doBrowse(profile, dn);
+        ldapBrowser.close();
+
+        LOGGER.trace(pwmRequest, "performed ldapBrowse operation in "
+                + TimeDuration.fromCurrent(startTime).asCompactString()
+                + ", result=" + JsonUtil.serialize(result));
+
+        pwmRequest.outputJsonResult(new RestResultBean(result));
+    }
+
     private void restUpdateLdapForm(
             final PwmRequest pwmRequest,
             final ConfigGuideBean configGuideBean
@@ -457,11 +497,11 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
                     configGuideBean.getFormData().putAll(defaultForm);
                     configGuideBean.setSelectedTemplate(template);
                     storedConfiguration.setTemplate(template);
-                    {   
+                    {
                         final String settingValue = AppProperty.LDAP_PROMISCUOUS_ENABLE.getKey() + "=true";
-                                storedConfiguration.writeSetting(
-                                        PwmSetting.APP_PROPERTY_OVERRIDES,
-                                        new StringArrayValue(Collections.singletonList(settingValue)),
+                        storedConfiguration.writeSetting(
+                                PwmSetting.APP_PROPERTY_OVERRIDES,
+                                new StringArrayValue(Collections.singletonList(settingValue)),
                                 null);
                     }
                 }
@@ -545,11 +585,7 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             throws PwmUnrecoverableException
     {
         {
-            final String ldapServerIP = ldapForm.get(PARAM_LDAP_HOST);
-            final String ldapServerPort = ldapForm.get(PARAM_LDAP_PORT);
-            final boolean ldapServerSecure = "true".equalsIgnoreCase(ldapForm.get(PARAM_LDAP_SECURE));
-
-            final String newLdapURI = "ldap" + (ldapServerSecure ? "s" : "") +  "://" + ldapServerIP + ":" + ldapServerPort;
+            final String newLdapURI = getLdapUrlFromFormConfig(ldapForm);
             final StringArrayValue newValue = new StringArrayValue(Collections.singletonList(newLdapURI));
             storedConfiguration.writeSetting(PwmSetting.LDAP_SERVER_URLS, LDAP_PROFILE_KEY, newValue, null);
         }
@@ -569,22 +605,22 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             if (ldapAdminDN != null && ldapAdminDN.contains(",")) {
                 contextDN = ldapAdminDN.substring(ldapAdminDN.indexOf(",") + 1,ldapAdminDN.length());
             }
-            ldapForm.put(PARAM_LDAP2_CONTEXT, contextDN);
+            ldapForm.put(PARAM_LDAP_CONTEXT, contextDN);
         }
-        storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_KEY, new StringArrayValue(Collections.singletonList(ldapForm.get(PARAM_LDAP2_CONTEXT))), null);
+        storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_KEY, new StringArrayValue(Collections.singletonList(ldapForm.get(PARAM_LDAP_CONTEXT))), null);
 
         {  // set context based on ldap dn
-            final String ldapContext = ldapForm.get(PARAM_LDAP2_CONTEXT);
+            final String ldapContext = ldapForm.get(PARAM_LDAP_CONTEXT);
             storedConfiguration.writeSetting(PwmSetting.LDAP_CONTEXTLESS_ROOT, LDAP_PROFILE_KEY, new StringArrayValue(Collections.singletonList(ldapContext)), null);
         }
 
         {  // set context based on ldap dn
-            final String ldapTestUserDN = ldapForm.get(PARAM_LDAP2_TEST_USER);
+            final String ldapTestUserDN = ldapForm.get(PARAM_LDAP_TEST_USER);
             storedConfiguration.writeSetting(PwmSetting.LDAP_TEST_USER_DN, LDAP_PROFILE_KEY, new StringValue(ldapTestUserDN), null);
         }
 
         {  // set admin query
-            final String groupDN = ldapForm.get(PARAM_LDAP2_ADMIN_GROUP);
+            final String groupDN = ldapForm.get(PARAM_LDAP_ADMIN_GROUP);
             final List<UserPermission> userPermissions = Collections.singletonList(new UserPermission(UserPermission.Type.ldapGroup, null, null, groupDN));
             storedConfiguration.writeSetting(PwmSetting.QUERY_MATCH_PWM_ADMIN, new UserPermissionValue(userPermissions), null);
         }
@@ -688,4 +724,31 @@ public class ConfigGuideServlet extends AbstractPwmServlet {
             LOGGER.error(pwmRequest, e.getMessage(), e);
         }
     }
+
+    private static String getLdapUrlFromFormConfig(final Map<String,String> ldapForm) {
+        final String ldapServerIP = ldapForm.get(PARAM_LDAP_HOST);
+        final String ldapServerPort = ldapForm.get(PARAM_LDAP_PORT);
+        final boolean ldapServerSecure = "true".equalsIgnoreCase(ldapForm.get(PARAM_LDAP_SECURE));
+
+        return "ldap" + (ldapServerSecure ? "s" : "") +  "://" + ldapServerIP + ":" + ldapServerPort;
+
+    }
+
+    private void checkLdapServer(ConfigGuideBean configGuideBean) throws PwmOperationalException, IOException {
+        final Map<String,String> formData = configGuideBean.getFormData();
+        final String host = formData.get(PARAM_LDAP_HOST);
+        final int port = Integer.parseInt(formData.get(PARAM_LDAP_PORT));
+        if (Boolean.parseBoolean(formData.get(PARAM_LDAP_SECURE))) {
+            X509Utils.readRemoteCertificates(host,port);
+        } else {
+            InetAddress addr = InetAddress.getByName(host);
+            SocketAddress sockaddr = new InetSocketAddress(addr, port);
+            Socket sock = new Socket();
+
+            // this method will block for the defined number of milliseconds
+            int timeout = 2000;
+            sock.connect(sockaddr, timeout);
+        }
+
+    }
 }

+ 1 - 1
pwm/servlet/src/password/pwm/http/servlet/SetupResponsesServlet.java

@@ -219,7 +219,7 @@ public class SetupResponsesServlet extends AbstractPwmServlet {
             );
             pwmApplication.getAuditManager().submit(auditRecord);
 
-            pwmRequest.sendRedirect(PwmServletDefinition.SetupResponses.servletUrl());
+            pwmRequest.sendRedirect(PwmServletDefinition.SetupResponses);
         } catch (PwmOperationalException e) {
             LOGGER.debug(pwmSession, e.getErrorInformation());
             pwmRequest.setResponseError(e.getErrorInformation());

+ 3 - 2
pwm/servlet/src/password/pwm/i18n/Health.properties

@@ -3,7 +3,7 @@
 # http://code.google.com/p/pwm/
 #
 # Copyright (c) 2006-2009 Novell, Inc.
-# Copyright (c) 2009-2015 The PWM Project
+# Copyright (c) 2009-2014 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
@@ -42,7 +42,7 @@ HealthMessage_Config_PromiscuousLDAP=%1% setting should be set to false for prop
 HealthMessage_Config_ShowDetailedErrors=%1% setting should be set to false for proper security
 HealthMessage_Config_AddTestUser=%1% setting should be set to verify proper operation
 HealthMessage_Config_ParseError=%1% error parsing setting %2%: %3%
-HealthMessage_Config_UsingLocalDBResponseStorage=The setting %1% is configured to store responses in the LocalDB.  This should never be used in a production environment.
+HealthMessage_Config_UsingLocalDBResponseStorage=The setting %1% is configured to store user data in the LocalDB.  This should never be used in a production environment.
 HealthMessage_Config_WeakPassword=%1% strength of password is weak (%2%/100); increase password length/complexity for proper security
 HealthMessage_Config_LDAPUnsecure=%1% url is configured for non-secure connection: %2%
 HealthMessage_Config_MissingDB=The configuration uses database storage, but database connection settings are not set
@@ -53,6 +53,7 @@ HealthMessage_Config_MissingProxyDN=Missing proxy user DN for profile %1%
 HealthMessage_Config_MissingProxyPassword=Missing proxy user password for profile %1%
 HealthMessage_Config_PasswordPolicyProblem=Password policy %1% configuration anomaly: %2%
 HealthMessage_Config_UserPermissionValidity=User Permission configuration for setting %1% issue: %2%
+HealthMessage_Config_Certificate=Certificate for setting %1% issue: %2%
 HealthMessage_LDAP_VendorsNotSame=LDAP directories of different vendor types are in use.  This configuration may cause undesirable side effects and is not supported.  %1%
 HealthMessage_LDAP_Ad_History_Asn_Missing=%1% is enabled, but the server at %2% does not support this feature.  Check to be sure it is upgraded to Windows Server 2008 R2 SP1 or greater.  Password changes against this server may fail until this is resolved.
 HealthMessage_LDAP_RecentlyUnreachable=LDAP profile %1% was recently unavailable (%2% ago at %3%): %4%

+ 270 - 0
pwm/servlet/src/password/pwm/ldap/LdapBrowser.java

@@ -0,0 +1,270 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 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.ldap;
+
+import com.novell.ldapchai.ChaiEntry;
+import com.novell.ldapchai.ChaiFactory;
+import com.novell.ldapchai.exception.ChaiOperationException;
+import com.novell.ldapchai.exception.ChaiUnavailableException;
+import com.novell.ldapchai.provider.ChaiProvider;
+import com.novell.ldapchai.util.ChaiUtility;
+import com.novell.ldapchai.util.SearchHelper;
+import password.pwm.AppProperty;
+import password.pwm.config.Configuration;
+import password.pwm.config.StoredConfiguration;
+import password.pwm.config.profile.LdapProfile;
+import password.pwm.error.PwmUnrecoverableException;
+
+import java.io.Serializable;
+import java.util.*;
+
+public class LdapBrowser {
+    final StoredConfiguration storedConfiguration;
+
+    private Map<String,ChaiProvider> providerCache = new HashMap<>();
+
+    public LdapBrowser(StoredConfiguration storedConfiguration) throws PwmUnrecoverableException {
+        this.storedConfiguration = storedConfiguration;
+    }
+
+    public LdapBrowseResult doBrowse(final String profile, final String dn) throws PwmUnrecoverableException {
+        try {
+            return doBrowseImpl(figureLdapProfileID(profile), dn);
+        } catch (ChaiUnavailableException | ChaiOperationException e) {
+            throw PwmUnrecoverableException.fromChaiException(e);
+        }
+    }
+
+    public void close() {
+        for (final ChaiProvider chaiProvider : providerCache.values()) {
+            chaiProvider.close();
+        }
+        providerCache.clear();
+    }
+
+    private LdapBrowseResult doBrowseImpl(final String profileID, final String dn) throws PwmUnrecoverableException, ChaiUnavailableException, ChaiOperationException {
+
+        final LdapBrowseResult result = new LdapBrowseResult();
+        {
+            final Map<String, Boolean> childDNs = new TreeMap<>();
+            childDNs.putAll(getChildEntries(profileID, dn));
+
+            for (final String childDN : childDNs.keySet()) {
+                final DNInformation dnInformation = new DNInformation();
+                dnInformation.setDn(childDN);
+                dnInformation.setEntryName(entryNameFromDN(childDN));
+                if (childDNs.get(childDN)) {
+                    result.getNavigableDNlist().add(dnInformation);
+                } else {
+                    result.getSelectableDNlist().add(dnInformation);
+                }
+            }
+            result.setMaxResults(childDNs.size() >= getMaxSizeLimit());
+
+        }
+        result.setDn(dn);
+        result.setProfileID(profileID);
+        Configuration configuration = new Configuration(storedConfiguration);
+        if (configuration.getLdapProfiles().size() > 1) {
+            result.getProfileList().addAll(configuration.getLdapProfiles().keySet());
+        }
+
+        if (adRootDNList(profileID).contains(dn)) {
+            result.setParentDN("");
+        } else if (dn != null && !dn.isEmpty()) {
+            final ChaiEntry dnEntry = ChaiFactory.createChaiEntry(dn, getChaiProvider(profileID));
+            final ChaiEntry parentEntry = dnEntry.getParentEntry();
+            if (parentEntry == null) {
+                result.setParentDN("");
+            } else {
+                result.setParentDN(parentEntry.getEntryDN());
+            }
+        }
+
+        return result;
+    }
+
+    private ChaiProvider getChaiProvider(final String profile) throws PwmUnrecoverableException {
+        if (!providerCache.containsKey(profile)) {
+            final Configuration configuration = new Configuration(storedConfiguration);
+            final LdapProfile ldapProfile = LdapProfile.makeFromStoredConfiguration(storedConfiguration, profile);
+            final ChaiProvider chaiProvider = LdapOperationsHelper.openProxyChaiProvider(null,ldapProfile,configuration,null);
+            providerCache.put(profile,chaiProvider);
+        }
+        return providerCache.get(profile);
+    }
+
+    private String figureLdapProfileID(final String profile) {
+        final Configuration configuration = new Configuration(storedConfiguration);
+        if (configuration.getLdapProfiles().keySet().contains(profile)) {
+            return profile;
+        }
+        return configuration.getLdapProfiles().keySet().iterator().next();
+    }
+
+    private int getMaxSizeLimit() {
+        final Configuration configuration = new Configuration(storedConfiguration);
+        return Integer.parseInt(configuration.readAppProperty(AppProperty.LDAP_BROWSER_MAX_ENTRIES));
+    }
+
+    private Map<String, Boolean> getChildEntries(
+            final String profile,
+            final String dn
+    )
+            throws ChaiUnavailableException, PwmUnrecoverableException, ChaiOperationException {
+        final HashMap<String, Boolean> returnMap = new HashMap<>();
+        final ChaiProvider chaiProvider = getChaiProvider(profile);
+        if ((dn == null || dn.isEmpty()) && chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+            final Set<String> adRootDNList = adRootDNList(profile);
+            for (final String rootDN : adRootDNList) {
+                returnMap.put(rootDN, true);
+            }
+        } else {
+
+            final Map<String, Map<String, List<String>>> results;
+            {
+                final SearchHelper searchHelper = new SearchHelper();
+                searchHelper.setFilter("(objectclass=*)");
+                searchHelper.setMaxResults(getMaxSizeLimit());
+                searchHelper.setAttributes("subordinateCount");
+                searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.ONE);
+                results = chaiProvider.searchMultiValues(dn, searchHelper);
+
+            }
+            for (final String resultDN : results.keySet()) {
+                boolean hasSubs;
+                if (results.get(resultDN).containsKey("subordinateCount")) { // only eDir actually returns this operational attribute
+                    Integer subordinateCount = Integer.parseInt(results.get(resultDN).get("subordinateCount").iterator().next());
+                    hasSubs = subordinateCount > 0;
+                } else {
+                    final SearchHelper searchHelper = new SearchHelper();
+                    searchHelper.setFilter("(objectclass=*)");
+                    searchHelper.setMaxResults(1);
+                    searchHelper.setAttributes(Collections.<String>emptyList());
+                    searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.ONE);
+                    final Map<String, Map<String, String>> subSearchResults = chaiProvider.search(resultDN, searchHelper);
+                    hasSubs = !subSearchResults.isEmpty();
+                }
+                returnMap.put(resultDN, hasSubs);
+            }
+        }
+        return returnMap;
+    }
+
+    private Set<String> adRootDNList(final String profile) throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException {
+        final ChaiProvider chaiProvider = getChaiProvider(profile);
+        final Set<String> adRootValues = new HashSet<>();
+        if (chaiProvider.getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) {
+            ChaiEntry chaiEntry = ChaiUtility.getRootDSE(chaiProvider);
+            adRootValues.addAll(chaiEntry.readMultiStringAttribute("namingContexts"));
+        }
+        return adRootValues;
+    }
+
+    private static String entryNameFromDN(final String dn) {
+        int start = dn.indexOf("=");
+        start = start == -1 ? 0 : start + 1;
+        int end = dn.indexOf(",");
+        if (end == -1) {
+            end = dn.length();
+        }
+
+        return dn.substring(start,end);
+    }
+
+    public class LdapBrowseResult implements Serializable {
+        private String dn;
+        private String profileID;
+        private String parentDN;
+        private List<String> profileList = new ArrayList<>();
+        private boolean maxResults;
+
+        private List<DNInformation> navigableDNlist = new ArrayList<>();
+        private List<DNInformation> selectableDNlist = new ArrayList<>();
+
+        public String getDn() {
+            return dn;
+        }
+
+        public void setDn(String dn) {
+            this.dn = dn;
+        }
+
+        public String getProfileID() {
+            return profileID;
+        }
+
+        public void setProfileID(String profileID) {
+            this.profileID = profileID;
+        }
+
+        public String getParentDN() {
+            return parentDN;
+        }
+
+        public void setParentDN(String parentDN) {
+            this.parentDN = parentDN;
+        }
+
+        public List<String> getProfileList() {
+            return profileList;
+        }
+
+        public boolean isMaxResults() {
+            return maxResults;
+        }
+
+        public void setMaxResults(boolean maxResults) {
+            this.maxResults = maxResults;
+        }
+
+        public List<DNInformation> getNavigableDNlist() {
+            return navigableDNlist;
+        }
+
+        public List<DNInformation> getSelectableDNlist() {
+            return selectableDNlist;
+        }
+    }
+
+    public static class DNInformation implements Serializable {
+        private String entryName;
+        private String dn;
+
+        public String getEntryName() {
+            return entryName;
+        }
+
+        public void setEntryName(String entryName) {
+            this.entryName = entryName;
+        }
+
+        public String getDn() {
+            return dn;
+        }
+
+        public void setDn(String dn) {
+            this.dn = dn;
+        }
+    }
+}

+ 1 - 1
pwm/servlet/src/password/pwm/ldap/LdapOperationsHelper.java

@@ -343,7 +343,7 @@ public class LdapOperationsHelper {
     )
             throws PwmUnrecoverableException
     {
-        final ChaiConfiguration chaiConfig = new ChaiConfiguration(ldapURLs, userDN, userPassword.getStringValue());
+        final ChaiConfiguration chaiConfig = new ChaiConfiguration(ldapURLs, userDN, userPassword == null ? null : userPassword.getStringValue());
 
         chaiConfig.setSetting(ChaiSetting.PROMISCUOUS_SSL, config.readAppProperty(AppProperty.LDAP_PROMISCUOUS_ENABLE));
         chaiConfig.setSetting(ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString(config.readSettingAsBoolean(PwmSetting.EDIRECTORY_ENABLE_NMAS)));

+ 1 - 1
pwm/servlet/src/password/pwm/util/cli/MainClass.java

@@ -378,7 +378,7 @@ public class MainClass {
         final PwmApplication pwmApplication = new PwmApplication.PwmEnvironment(config, applicationPath)
                 .setApplicationMode(mode)
                 .setApplicationPathType(MAIN_OPTIONS.applicationPathType)
-                .setInitLogging(false)
+                .setInternalRuntimeInstance(true)
                 .setConfigurationFile(configurationFile)
                 .setWebInfPath(null)
                 .createPwmApplication();

+ 1 - 1
pwm/servlet/web/WEB-INF/jsp/configeditor.jsp

@@ -64,7 +64,6 @@
             </div>
         </div>
     </div>
-
     <div id="centerbody-config" class="centerbody-config">
         <div id="settingSearchPanel">
             <table class="noborder settingSearchPanelTable">
@@ -152,6 +151,7 @@
     </script>
 </pwm:script>
 <pwm:script-ref url="/public/resources/js/configmanager.js"/>
+<pwm:script-ref url="/public/resources/js/uilibrary.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>

+ 0 - 1
pwm/servlet/web/WEB-INF/jsp/configguide-app.jsp

@@ -61,7 +61,6 @@
                         <div id="titlePane_<%=ConfigGuideServlet.PARAM_APP_SITEURL%>" style="padding-left: 5px; padding-top: 5px">
                             <b>Site URL</b>
                             <br/>
-                            <span class="fa fa-chevron-circle-right"></span>
                             <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_APP_SITEURL%>" name="<%=ConfigGuideServlet.PARAM_APP_SITEURL%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_APP_SITEURL))%>" required autofocus/>
                         </div>
                     </div>

+ 3 - 3
pwm/servlet/web/WEB-INF/jsp/configguide-end.jsp

@@ -105,21 +105,21 @@
                         </td>
                         <td>
                             <%=StringUtil.escapeHtml(
-                                    configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_CONTEXT))%>
+                                    configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_CONTEXT))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>Administrator Search Filter</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_ADMIN_GROUP))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP))%>
                         </td>
                     </tr>
                     <tr>
                         <td><b>LDAP Test User DN</b>
                         </td>
                         <td>
-                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_TEST_USER))%>
+                            <%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_TEST_USER))%>
                         </td>
                     </tr>
                     <tr>

+ 162 - 0
pwm/servlet/web/WEB-INF/jsp/configguide-ldap_admin.jsp

@@ -0,0 +1,162 @@
+<%@ page import="password.pwm.http.bean.ConfigGuideBean" %>
+<%@ page import="password.pwm.http.servlet.ConfigGuideServlet" %>
+<%@ page import="java.util.Map" %>
+<%--
+  ~ Password Management Servlets (PWM)
+  ~ http://code.google.com/p/pwm/
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2015 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
+  --%>
+
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_LOCALE); %>
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_THEME); %>
+<!DOCTYPE html>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<% ConfigGuideBean configGuideBean = (ConfigGuideBean) JspUtility.getPwmSession(pageContext).getSessionBean(ConfigGuideBean.class);%>
+<% Map<String,String> DEFAULT_FORM = ConfigGuideServlet.defaultForm(configGuideBean.getStoredConfiguration().getTemplate()); %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<html dir="<pwm:LocaleOrientation/>">
+<%@ include file="fragment/header.jsp" %>
+<body class="nihilo">
+<link href="<pwm:context/><pwm:url url='/public/resources/configStyle.css'/>" rel="stylesheet" type="text/css"/>
+<div id="wrapper">
+    <div id="header">
+        <div id="header-center">
+            <div id="header-page">
+                <pwm:display key="Title_ConfigGuide" bundle="Config"/>
+            </div>
+            <div id="header-title">
+                <pwm:display key="Title_ConfigGuide_ldap" bundle="Config"/>
+            </div>
+        </div>
+    </div>
+    <div id="centerbody">
+        <form id="configForm" name="configForm">
+            <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+            <div id="outline_ldap-user" class="setting_outline">
+                <div id="titlePaneHeader-ldap-credentials" class="setting_title">
+                    LDAP Credentials
+                </div>
+                <div class="setting_body">
+                    <p>Enter the credentials for your ldap server.  You must enter the fully qualified LDAP DN of the
+                    admin account here.  In most cases, you should use an account created specially for this purpose, with sufficient rights to
+                    administer the users that will be logging into this system.</p>
+                    <p>The browse feature for this page requires that your LDAP directory permit anonymous bind.</p>
+
+                    <div class="setting_item">
+                        <label>
+                            <b>Proxy/Admin LDAP DN</b>
+                            <br/>
+                            <button type="button" class="btn" id="button-browse-adminDN"><span class="btn-icon fa fa-sitemap"></span>Browse</button>
+                            <input type="text" style="width:400px" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_DN)%>"/>
+                        </label>
+                    </div>
+                    &nbsp;<br/>
+                    <div class="setting_item">
+                        <label>
+                            <b>Password</b>
+                            <br/>
+                            <input style="width:200px" class="configStringInput passwordfield" type="password" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_PW)%>"/>
+                        </label>
+                    </div>
+                </div>
+            </div>
+        </form>
+        <br/>
+        <div id="healthBody" style="border:0; margin:0; padding:0; cursor: pointer">
+            <div style="text-align: center">
+                <button class="menubutton" style="margin-left: auto; margin-right: auto">
+                    <pwm:if test="showIcons"><span class="btn-icon fa fa-check"></span></pwm:if>
+                    <pwm:display key="Button_CheckSettings" bundle="Config"/>
+                </button>
+            </div>
+        </div>
+        <div class="buttonbar">
+            <button class="btn" id="button_previous">
+                <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                <pwm:display key="Button_Previous" bundle="Config"/>
+            </button>
+            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+            <button class="btn" id="button_next">
+                <pwm:if test="showIcons"><span class="btn-icon fa fa-forward"></span></pwm:if>
+                <pwm:display key="Button_Next" bundle="Config"/>
+            </button>
+        </div>
+    </div>
+    <div class="push"></div>
+</div>
+<pwm:script>
+    <script type="text/javascript">
+        function handleFormActivity() {
+            PWM_GUIDE.updateForm();
+            clearHealthDiv();
+        }
+
+        function clearHealthDiv() {
+            PWM_MAIN.getObject('healthBody').innerHTML = PWM_VAR['originalHealthBody'];
+        }
+
+        PWM_GLOBAL['startupFunctions'].push(function(){
+            PWM_VAR['originalHealthBody'] = PWM_MAIN.getObject('healthBody').innerHTML;
+            clearHealthDiv();
+            checkIfNextEnabled();
+
+            PWM_MAIN.addEventHandler('configForm','input',function(){
+                handleFormActivity();
+            });
+
+            PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
+            PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
+
+            PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
+
+            PWM_MAIN.addEventHandler('button-browse-adminDN','click',function(){
+                UILibrary.editLdapDN(function(value){
+                    PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>').value = value;
+                    handleFormActivity();
+                })
+            });
+        });
+
+        function checkIfNextEnabled() {
+            PWM_MAIN.getObject('button_next').disabled = PWM_GLOBAL['pwm-health'] !== 'GOOD';
+        }
+
+        function loadHealth() {
+            console.log('loadHealth()');
+            var options = {};
+            options['sourceUrl'] = 'ConfigGuide?processAction=ldapHealth';
+            options['showRefresh'] = false;
+            options['refreshTime'] = -1;
+            options['finishFunction'] = function(){
+                PWM_MAIN.closeWaitDialog();
+                checkIfNextEnabled();
+            };
+            PWM_MAIN.showWaitDialog({loadFunction:function(){
+                PWM_ADMIN.showAppHealth('healthBody', options);
+            }});
+        }
+    </script>
+</pwm:script>
+<pwm:script-ref url="/public/resources/js/configguide.js"/>
+<pwm:script-ref url="/public/resources/js/uilibrary.js"/>
+<pwm:script-ref url="/public/resources/js/configmanager.js"/>
+<pwm:script-ref url="/public/resources/js/admin.js"/>
+<%@ include file="fragment/footer.jsp" %>
+</body>
+</html>

+ 190 - 0
pwm/servlet/web/WEB-INF/jsp/configguide-ldap_cert.jsp

@@ -0,0 +1,190 @@
+<%@ page import="password.pwm.http.bean.ConfigGuideBean" %>
+<%@ page import="password.pwm.http.servlet.ConfigGuideServlet" %>
+<%@ page import="password.pwm.util.StringUtil" %>
+<%@ page import="password.pwm.util.X509Utils" %>
+<%@ page import="password.pwm.util.secure.PwmHashAlgorithm" %>
+<%@ page import="password.pwm.util.secure.SecureEngine" %>
+<%@ page import="java.io.ByteArrayInputStream" %>
+<%@ page import="java.security.cert.X509Certificate" %>
+<%--
+  ~ Password Management Servlets (PWM)
+  ~ http://code.google.com/p/pwm/
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2015 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
+  --%>
+
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_LOCALE); %>
+<% JspUtility.setFlag(pageContext, PwmRequest.Flag.HIDE_THEME); %>
+<!DOCTYPE html>
+<%@ page language="java" session="true" isThreadSafe="true" contentType="text/html" %>
+<% ConfigGuideBean configGuideBean = JspUtility.getPwmSession(pageContext).getSessionBean(ConfigGuideBean.class);%>
+<% boolean enableNext = configGuideBean.isCertsTrustedbyKeystore() || configGuideBean.isUseConfiguredCerts() || configGuideBean.getLdapCertificates() == null; %>
+<%@ taglib uri="pwm" prefix="pwm" %>
+<html dir="<pwm:LocaleOrientation/>">
+<%@ include file="fragment/header.jsp" %>
+<body class="nihilo">
+<link href="<pwm:context/><pwm:url url='/public/resources/configStyle.css'/>" rel="stylesheet" type="text/css"/>
+<div id="wrapper">
+    <div id="header">
+        <div id="header-center">
+            <div id="header-page">
+                <pwm:display key="Title_ConfigGuide" bundle="Config"/>
+            </div>
+            <div id="header-title">
+                <pwm:display key="Title_ConfigGuide_ldapcert" bundle="Config"/>
+            </div>
+        </div>
+    </div>
+    <div id="centerbody">
+        <% if (configGuideBean.getLdapCertificates() == null) { %>
+        <div>
+            <pwm:display key="Display_ConfigGuideNotSecureLDAP" bundle="Config"/>
+        </div>
+        <% } else { %>
+        <form id="formData" data-dojo-type="dijit/form/Form">
+            <%@ include file="/WEB-INF/jsp/fragment/message.jsp" %>
+            <br class="clear"/>
+            <div id="outline_ldap-server" class="setting_outline">
+                <div class="setting_title">
+                    LDAP Server Certificates
+                </div>
+                <div class="setting_body">
+                    The following are the LDAP server certificates read from the server at
+                    <b><%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_HOST)%>:<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_PORT)%></b>.
+                    Please verify these certificates match your LDAP server.
+                    <div>
+                        <div id="titlePane_<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" style="padding-left: 5px; padding-top: 5px">
+                            <% int counter=0;for (X509Certificate certificate : configGuideBean.getLdapCertificates()) {%>
+                            <% final String md5sum = SecureEngine.hash(new ByteArrayInputStream(certificate.getEncoded()), PwmHashAlgorithm.MD5); %>
+                            <% final String sha1sum = SecureEngine.hash(new ByteArrayInputStream(certificate.getEncoded()), PwmHashAlgorithm.SHA1); %>
+                            <table style="width:100%" id="table_certificate0">
+                                <tr><td colspan="2" class="key" style="text-align: center">
+                                    Certificate <%=counter%>&nbsp;<a style="font-size: smaller" href="#" id="button-showCert_<%=md5sum%>">(details)</a>
+                                </td></tr>
+                                <tr><td>Subject Name</td><td><div class="setting_table_value"><%=certificate.getSubjectX500Principal().getName()%></div></td></tr>
+                                <tr><td>Issuer Name</td><td><div class="setting_table_value"><%=certificate.getIssuerX500Principal().getName()%></div></td></tr>
+                                <% final String serialNum = X509Utils.hexSerial(certificate); %>
+                                <tr><td>Serial Number</td><td><div class="setting_table_value"><%=serialNum%></div></td></tr>
+                                <tr><td>Issue Date</td><td><div class="setting_table_value timestamp"><%=PwmConstants.DEFAULT_DATETIME_FORMAT.format(certificate.getNotBefore())%></div></td></tr>
+                                <tr><td>Expire Date</td><td><div class="setting_table_value timestamp"><%=PwmConstants.DEFAULT_DATETIME_FORMAT.format(certificate.getNotAfter())%></div></td></tr>
+                            </table>
+                            <pwm:script>
+                                <script type="text/javascript">
+                                    PWM_GLOBAL['startupFunctions'].push(function(){
+                                        PWM_MAIN.addEventHandler('button-showCert_<%=md5sum%>','click',function(){
+                                            var body = '<pre style="white-space: pre-wrap; word-wrap: break-word">';
+                                            body += 'md5sum: <%=md5sum%>\n';
+                                            body += 'sha1sum: <%=sha1sum%>\n';
+                                            body += '<%=StringUtil.escapeJS(certificate.toString())%>';
+                                            body += '</pre>';
+                                            PWM_MAIN.showDialog({
+                                                title: "Certificate <%=counter%> Detail",
+                                                text: body,
+                                                showClose: true,
+                                                showOk: false,
+                                                dialogClass:'wide'
+                                            });
+                                        });
+                                    });
+                                </script>
+                            </pwm:script>
+                            <% counter++; } %>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <br class="clear"/>
+            <div id="outline_ldapcert-options" class="setting_outline">
+                <div class="setting_title">Certificate Settings</div>
+                <div class="setting_body">
+                    <div style="padding-left: 5px; padding-top: 5px">
+                        At least one of the following options must be selected to continue.
+                    </div>
+                    <br/>
+                    <div id="titlePane_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" style="padding-left: 5px; padding-top: 5px">
+                        Certificate(s) are trusted by default Java keystore
+                        <br/>
+                        <button id="button_defaultTrustStore">Enabled</button> (Import/remove certificate manually into Java keystore to change)
+                    </div>
+                    <div id="titlePane_<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" style="padding-left: 5px; padding-top: 5px">
+                        Use application to manage certificate(s) and automatically import certificates into configuration file
+                        <br/>
+                        <button id="button_useConfig">Enabled</button>
+                    </div>
+                </div>
+                <pwm:script>
+                <script type="text/javascript">
+                    PWM_GLOBAL['startupFunctions'].push(function(){
+                        require(["dijit/form/ToggleButton"],function(ToggleButton){
+                            new ToggleButton({
+                                id: 'button_defaultTrustStore',
+                                iconClass:'dijitCheckBoxIcon',
+                                showLabel: 'Enabled',
+                                disabled: true,
+                                checked: <%=configGuideBean.isCertsTrustedbyKeystore()%>
+                            },'button_defaultTrustStore');
+
+                            new ToggleButton({
+                                id: 'button_useConfig',
+                                iconClass:'dijitCheckBoxIcon',
+                                showLabel: 'Enabled',
+                                checked: <%=configGuideBean.isUseConfiguredCerts()%>,
+                                onChange: function(){
+                                    PWM_GUIDE.setUseConfiguredCerts(<%=!configGuideBean.isUseConfiguredCerts()%>);
+                                }
+                            },'button_useConfig');
+                        });
+                    });
+                </script>
+                </pwm:script>
+            </div>
+        </form>
+        <% } %>
+        <br/>
+        <div class="buttonbar">
+            <button class="btn" id="button_previous">
+                <pwm:if test="showIcons"><span class="btn-icon fa fa-backward"></span></pwm:if>
+                <pwm:display key="Button_Previous" bundle="Config"/>
+            </button>
+            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+            <button class="btn" id="button_next" <%=enableNext?"":" disabled=\"disabled\""%>>
+                <pwm:if test="showIcons"><span class="btn-icon fa fa-forward"></span></pwm:if>
+                <pwm:display key="Button_Next" bundle="Config"/>
+            </button>
+        </div>
+    </div>
+    <div class="push"></div>
+</div>
+<pwm:script>
+<script type="text/javascript">
+    PWM_GLOBAL['startupFunctions'].push(function(){
+        require(["dojo/parser","dijit/TitlePane","dijit/form/Form","dijit/form/ValidationTextBox","dijit/form/NumberSpinner","dijit/form/CheckBox"],function(dojoParser){
+            dojoParser.parse();
+        });
+
+        PWM_MAIN.addEventHandler('button_next','click',function(){PWM_GUIDE.gotoStep('NEXT')});
+        PWM_MAIN.addEventHandler('button_previous','click',function(){PWM_GUIDE.gotoStep('PREVIOUS')});
+    });
+</script>
+</pwm:script>
+<pwm:script-ref url="/public/resources/js/configguide.js"/>
+<pwm:script-ref url="/public/resources/js/configmanager.js"/>
+<pwm:script-ref url="/public/resources/js/admin.js"/>
+<%@ include file="fragment/footer.jsp" %>
+</body>
+</html>

+ 18 - 4
pwm/servlet/web/WEB-INF/jsp/configguide-ldap2.jsp → pwm/servlet/web/WEB-INF/jsp/configguide-ldap_context.jsp

@@ -61,8 +61,8 @@
                     <div class="setting_item">
                         <b>LDAP Contextless Login Root</b>
                         <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
-                        <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP2_CONTEXT%>" name="<%=ConfigGuideServlet.PARAM_LDAP2_CONTEXT%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_CONTEXT))%>" placeholder="<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP2_CONTEXT)%>" required autofocus/>
+                        <button type="button" class="btn" id="button-browse-context"><span class="btn-icon fa fa-sitemap"></span>Browse</button>
+                        <input style="width:400px" class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_CONTEXT%>" name="<%=ConfigGuideServlet.PARAM_LDAP_CONTEXT%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_CONTEXT))%>" placeholder="<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP_CONTEXT)%>" required autofocus/>
                     </div>
                 </div>
             </div>
@@ -77,8 +77,8 @@
                     <div class="setting_item">
                         <b>Administrator Group DN</b>
                         <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
-                        <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP2_ADMIN_GROUP%>" name="<%=ConfigGuideServlet.PARAM_LDAP2_ADMIN_GROUP%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_ADMIN_GROUP))%>" placeholder="<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP2_ADMIN_GROUP)%>" required/>
+                        <button type="button" class="btn" id="button-browse-adminGroup"><span class="btn-icon fa fa-sitemap"></span>Browse</button>
+                        <input style="width:400px;" class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP))%>" placeholder="<%=DEFAULT_FORM.get(ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP)%>" required/>
                         <button type="button" id="button-viewAdminMatches" class="btn">
                             <span class="btn-icon fa fa-eye"></span>
                             View Matches
@@ -148,6 +148,19 @@
                     PWM_MAIN.ajaxRequest(url,loadFunction);
                 }});
             });
+
+            PWM_MAIN.addEventHandler('button-browse-context','click',function(){
+                UILibrary.editLdapDN(function(value){
+                    PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_CONTEXT%>').value = value;
+                    handleFormActivity();
+                })
+            });
+            PWM_MAIN.addEventHandler('button-browse-adminGroup','click',function(){
+                UILibrary.editLdapDN(function(value){
+                    PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_GROUP%>').value = value;
+                    handleFormActivity();
+                })
+            });
         });
 
         function checkIfNextEnabled() {
@@ -174,6 +187,7 @@
     </script>
 </pwm:script>
 <pwm:script-ref url="/public/resources/js/configguide.js"/>
+<pwm:script-ref url="/public/resources/js/uilibrary.js"/>
 <pwm:script-ref url="/public/resources/js/configmanager.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>
 <%@ include file="fragment/footer.jsp" %>

+ 0 - 27
pwm/servlet/web/WEB-INF/jsp/configguide-ldap.jsp → pwm/servlet/web/WEB-INF/jsp/configguide-ldap_server.jsp

@@ -64,7 +64,6 @@
                         </tr>
                         <tr>
                             <td colspan="2">
-                                <span class="fa fa-chevron-circle-right"></span>
                                 <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" name="<%=ConfigGuideServlet.PARAM_LDAP_HOST%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_HOST)%>" <pwm:autofocus/> />
                             </td>
                         </tr>
@@ -79,12 +78,10 @@
                         </tr>
                         <tr>
                             <td>
-                                <span class="fa fa-chevron-circle-right"></span>
                                 <input class="configNumericInput" type="number" id="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" name="<%=ConfigGuideServlet.PARAM_LDAP_PORT%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_PORT)%>"/>
                             </td>
                             <td>
                                 <% boolean secureChecked = "true".equalsIgnoreCase(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_SECURE));%>
-                                <span class="fa fa-chevron-circle-right"></span>
                                 <label class="checkboxWrapper">
                                     <input type="checkbox" id="widget_<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" name="nope" <%=secureChecked ? "checked" : ""%>/> Secure
                                     <input type="hidden" id="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" name="<%=ConfigGuideServlet.PARAM_LDAP_SECURE%>" value="uninitialized"/>
@@ -94,30 +91,6 @@
                     </table>
                 </div>
             </div>
-            <br/>
-            <div id="outline_ldap-user" class="setting_outline">
-                <div id="titlePaneHeader-ldap-credentials" class="setting_title">
-                    LDAP Credentials
-                </div>
-                <div class="setting_body">
-                    Enter the credentials for your ldap server.  You must enter the fully qualified LDAP DN of the
-                    admin account here.  In most cases, you should use an account created specially for this purpose, with sufficient rights to
-                    administer the users that will be logging into this system.
-                    <div class="setting_item">
-                        <b>Proxy/Admin LDAP DN</b>
-                        <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
-                        <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_DN%>"  value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_DN)%>"/>
-                    </div>
-                    &nbsp;<br/>
-                    <div class="setting_item">
-                        <b>Password</b>
-                        <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
-                        <input class="configStringInput" type="password" id="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" name="<%=ConfigGuideServlet.PARAM_LDAP_ADMIN_PW%>" value="<%=configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_ADMIN_PW)%>"/>
-                    </div>
-                </div>
-            </div>
         </form>
         <br/>
         <div id="healthBody" style="border:0; margin:0; padding:0; cursor: pointer">

+ 12 - 4
pwm/servlet/web/WEB-INF/jsp/configguide-ldap3.jsp → pwm/servlet/web/WEB-INF/jsp/configguide-ldap_testuser.jsp

@@ -61,11 +61,11 @@
                     <br/><br/>
                     This setting is optional but recommended.  If you do not wish to configure an LDAP Test User DN at this time, you can leave this setting blank and configure a test user later.
                     <div class="setting_item">
-                        <div id="titlePane_<%=ConfigGuideServlet.PARAM_LDAP2_TEST_USER%>" style="padding-left: 5px; padding-top: 5px">
+                        <div id="titlePane_<%=ConfigGuideServlet.PARAM_LDAP_TEST_USER%>" style="padding-left: 5px; padding-top: 5px">
                             <b>LDAP Test User DN</b>
                             <br/>
-                            <span class="fa fa-chevron-circle-right"></span>
-                            <input class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP2_TEST_USER%>" name="<%=ConfigGuideServlet.PARAM_LDAP2_TEST_USER%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP2_TEST_USER))%>" autofocus/>
+                            <button type="button" class="btn" id="button-browse-testUser"><span class="btn-icon fa fa-sitemap"></span>Browse</button>
+                            <input style="width:400px" class="configStringInput" id="<%=ConfigGuideServlet.PARAM_LDAP_TEST_USER%>" name="<%=ConfigGuideServlet.PARAM_LDAP_TEST_USER%>" value="<%=StringUtil.escapeHtml(configGuideBean.getFormData().get(ConfigGuideServlet.PARAM_LDAP_TEST_USER))%>" autofocus/>
                         </div>
                     </div>
                 </div>
@@ -115,10 +115,17 @@
 
         PWM_MAIN.addEventHandler('configForm','input',function(){handleFormActivity()});
         PWM_MAIN.addEventHandler('healthBody','click',function(){loadHealth()});
+
+        PWM_MAIN.addEventHandler('button-browse-testUser','click',function(){
+            UILibrary.editLdapDN(function(value){
+                PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_TEST_USER%>').value = value;
+                handleFormActivity();
+            })
+        });
     });
 
     function checkIfNextEnabled() {
-        var fieldValue = PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP2_TEST_USER%>').value;
+        var fieldValue = PWM_MAIN.getObject('<%=ConfigGuideServlet.PARAM_LDAP_TEST_USER%>').value;
         PWM_MAIN.getObject('button_next').disabled = false;
         if (fieldValue.length && fieldValue.length > 0) {
             if (PWM_GLOBAL['pwm-health'] !== 'GOOD' && PWM_GLOBAL['pwm-health'] !== 'CONFIG') {
@@ -143,6 +150,7 @@
 </script>
 </pwm:script>
 <pwm:script-ref url="/public/resources/js/configguide.js"/>
+<pwm:script-ref url="/public/resources/js/uilibrary.js"/>
 <pwm:script-ref url="/public/resources/js/configmanager.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>
 <%@ include file="fragment/footer.jsp" %>

+ 0 - 2
pwm/servlet/web/WEB-INF/jsp/configguide-password.jsp

@@ -57,13 +57,11 @@
                     <div class="setting_item">
                         <b>Configuration Password</b>
                         <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
                         <input type="<pwm:value name="passwordFieldType"/>" id="<%=ConfigGuideServlet.PARAM_CONFIG_PASSWORD%>" name="<%=ConfigGuideServlet.PARAM_CONFIG_PASSWORD%>" class="configStringInput passwordfield" style="width:200px" <pwm:autofocus/>/>
                     </div>
                     <div class="setting_item">
                         <b>Verify Configuration Password</b>
                         <br/>
-                        <span class="fa fa-chevron-circle-right"></span>
                         <input type="<pwm:value name="passwordFieldType"/>" id="<%=ConfigGuideServlet.PARAM_CONFIG_PASSWORD_VERIFY%>" name="<%=ConfigGuideServlet.PARAM_CONFIG_PASSWORD_VERIFY%>" class="configStringInput passwordfield" style="width:200px"/>
                         <div style="display: inline; padding-top:45px;">
                             <img style="visibility:hidden;" id="confirmCheckMark" alt="checkMark" height="15" width="15"

+ 11 - 0
pwm/servlet/web/public/resources/configStyle.css

@@ -20,6 +20,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+body {
+    background-color: white;
+}
 
 .border {
     border: 1px solid #D4D4D4 !important;
@@ -537,3 +540,11 @@ button {
     right: 4px;
     cursor: pointer
 }
+
+.navigableDN {
+    cursor: pointer;
+}
+
+.selectableDN {
+    cursor: pointer;
+}

+ 63 - 265
pwm/servlet/web/public/resources/js/configeditor-settings.js

@@ -317,6 +317,7 @@ StringArrayValueHandler.drawRow = function(settingKey, iteration, value, itemCou
 };
 
 StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
+    var isLdapDN = PWM_SETTINGS['settings'][settingKey]['options']['ldapDNsyntax'] == 'true';
     var okAction = function(value) {
         if (iteration > -1) {
             PWM_VAR['clientSettingCache'][settingKey][iteration] = value;
@@ -332,7 +333,13 @@ StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
     editorOptions['placeholder'] = PWM_SETTINGS['settings'][settingKey]['placeholder'];
     editorOptions['completeFunction'] = okAction;
     editorOptions['value'] = iteration > -1 ? PWM_VAR['clientSettingCache'][settingKey][iteration] : '';
-    UILibrary.stringEditorDialog(editorOptions);
+    var isLdapDN = PWM_SETTINGS['settings'][settingKey]['options']['ldapDNsyntax'] == 'true';
+    if (isLdapDN) {
+        UILibrary.editLdapDN(okAction);
+    } else {
+        UILibrary.stringEditorDialog(editorOptions);
+    }
+
 };
 
 StringArrayValueHandler.move = function(settingKey, moveUp, iteration) {
@@ -2183,26 +2190,25 @@ UserPermissionHandler.draw = function(keyName) {
             var currentProfileValue = ('ldapProfileID' in resultValue[rowKey]) ? resultValue[rowKey]['ldapProfileID'] : "";
             htmlBody += '</div><table class="noborder">'
                 + '<td style="width:200px" id="' + inputID + '_profileHeader' + '">' + PWM_CONFIG.showString('Setting_Permission_Profile') + '</td>'
+                + '<td></td>'
                 + '<td><input style="width: 200px;" class="configStringInput" id="' + inputID + '-profile" list="' + inputID + '-datalist" value="' +  currentProfileValue + '"/>'
                 + '<datalist id="' + inputID + '-datalist"/></td>'
                 + '</tr>';
 
             if (resultValue[rowKey]['type'] != 'ldapGroup') {
-                var currentQueryValue = ('ldapQuery' in resultValue[rowKey]) ? resultValue[rowKey]['ldapQuery'] : "";
                 htmlBody += '<tr>'
                     + '<td><span id="' + inputID + '_FilterHeader' + '">' + PWM_CONFIG.showString('Setting_Permission_Filter') + '</span></td>'
-                    + '<td><input style="width: 420px;" class="configStringInput" id="' + inputID + '-query" value="' + currentQueryValue + '"></input></td>'
+                    + '<td id="icon-edit-query-' + inputID + '"><div title="Edit Value" class="btn-icon fa fa-edit"></div></td>'
+                    + '<td><div style="width: 350px;" class="configStringPanel noWrapTextBox border" id="' + inputID + '-query"></div></td>'
                     + '</tr>';
             }
 
-            var currentBaseValue = ('ldapBase' in resultValue[rowKey]) ? resultValue[rowKey]['ldapBase'] : "";
             htmlBody += '<tr>'
                 + '<td><span id="' + inputID + '_BaseHeader' + '">'
                 + PWM_CONFIG.showString((resultValue[rowKey]['type'] == 'ldapGroup') ?  'Setting_Permission_Base_Group' : 'Setting_Permission_Base')
                 + '</span></td>'
-
-                + '<td><input style="width: 420px;" class="configStringInput" id="' + inputID + '-base" value="' + currentBaseValue + '"></input></td>'
-                + '</td>'
+                + '<td id="icon-edit-base-' + inputID + '"><div title="Edit Value" class="btn-icon fa fa-edit"></div></td>'
+                + '<td><div style="width: 350px;" class="configStringPanel noWrapTextBox border" id="' + inputID + '-base">&nbsp;</div></td>'
                 + '</tr>';
 
 
@@ -2231,25 +2237,43 @@ UserPermissionHandler.draw = function(keyName) {
                 });
 
                 if (resultValue[rowKey]['type'] != 'ldapGroup') {
-                    var queryInput = PWM_MAIN.getObject(inputID + "-query");
-                    queryInput.disabled = false;
-                    queryInput.required = true;
+                    UILibrary.addTextValueToElement(inputID + '-query', PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery']);
+                    var queryEditor = function(){
+                        UILibrary.stringEditorDialog({
+                            value:PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'],
+                            completeFunction:function(value) {
+                                PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'] = value;
+                                UserPermissionHandler.write(keyName,true);
+                            }
+                        });
+                    };
 
-                    PWM_MAIN.addEventHandler(inputID + "-query",'input',function(){
-                        PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapQuery'] = this.value;
-                        UserPermissionHandler.write(keyName);
+                    PWM_MAIN.addEventHandler(inputID + "-query",'click',function(){
+                        queryEditor();
+                    });
+                    PWM_MAIN.addEventHandler('icon-edit-query-' + inputID,'click',function(){
+                        queryEditor();
                     });
                 }
 
-                var queryInput = PWM_MAIN.getObject(inputID + "-base");
-                queryInput.disabled = false;
-                queryInput.required = true;
-
-                PWM_MAIN.addEventHandler(inputID + "-base",'input',function(){
-                    PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = this.value;
-                    UserPermissionHandler.write(keyName);
+                var currentBaseValue = ('ldapBase' in resultValue[rowKey]) ? resultValue[rowKey]['ldapBase'] : "";
+                var baseEditor = function(){
+                    UILibrary.editLdapDN(function(value) {
+                        PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value;
+                        UserPermissionHandler.write(keyName,true);
+                    });
+                };
+                if (currentBaseValue && currentBaseValue.length > 0) {
+                    UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue);
+                }
+                PWM_MAIN.addEventHandler(inputID + '-base','click',function(){
+                    baseEditor();
+                });
+                PWM_MAIN.addEventHandler('icon-edit-base-' + inputID,'click',function(){
+                    baseEditor();
                 });
 
+
                 var deleteButtonID = 'button-' + inputID + '-deleteRow';
                 var hasID = PWM_MAIN.getObject(deleteButtonID) ? "YES" : "NO";
                 console.log("addEventHandler row: " + deleteButtonID + " rowKey=" + rowKey + " hasID="+hasID);
@@ -2485,19 +2509,26 @@ StringValueHandler.init = function(settingKey) {
             });
         });
 
+
+        var isLdapDN = settingData['options']['ldapDNsyntax'] == 'true';
         var editor = function(){
-            UILibrary.stringEditorDialog({
-                title:'Edit Value - ' + settingData['label'],
-                textarea:('TEXT_AREA' == settingData['syntax']),
-                regex:'pattern' in settingData ? settingData['pattern'] : '.+',
-                placeholder:settingData['placeholder'],
-                value:value,
-                completeFunction:function(value){
-                    PWM_CFGEDIT.writeSetting(settingKey,value,function(){
-                        StringValueHandler.init(settingKey);
-                    });
-                }
-            });
+            var writeBackFunc = function(value){
+                PWM_CFGEDIT.writeSetting(settingKey,value,function(){
+                    StringValueHandler.init(settingKey);
+                });
+            };
+            if (isLdapDN) {
+                UILibrary.editLdapDN(writeBackFunc);
+            } else {
+                UILibrary.stringEditorDialog({
+                    title:'Edit Value - ' + settingData['label'],
+                    textarea:('TEXT_AREA' == settingData['syntax']),
+                    regex:'pattern' in settingData ? settingData['pattern'] : '.+',
+                    placeholder:settingData['placeholder'],
+                    value:value,
+                    completeFunction:writeBackFunc
+                });
+            }
         };
 
         PWM_MAIN.addEventHandler('button-' + settingKey,'click',function(){
@@ -2843,236 +2874,3 @@ FileValueHandler.uploadFile = function(keyName) {
     PWM_CONFIG.uploadFile(options);
 };
 
-// -------------------------- common elements handler ------------------------------------
-
-
-var UILibrary = {};
-UILibrary.stringEditorDialog = function(options){
-    options = options === undefined ? {} : options;
-    var title = 'title' in options ? options['title'] : 'Edit Value';
-    var completeFunction = 'completeFunction' in options ? options['completeFunction'] : function() {alert('no string editor dialog complete function')};
-    var regexString = 'regex' in options && options['regex'] ? options['regex'] : '.+';
-    var initialValue = 'value' in options ? options['value'] : '';
-    var placeholder = 'placeholder' in options ? options['placeholder'] : '';
-    var textarea = 'textarea' in options ? options['textarea'] : false;
-
-    var regexObject = new RegExp(regexString);
-    var text = '';
-    text += '<div style="visibility: hidden;" id="panel-valueWarning"><span class="fa fa-warning message-error"></span>&nbsp;' + PWM_CONFIG.showString('Warning_ValueIncorrectFormat') + '</div>';
-    text += '<br/>';
-
-    if (textarea) {
-        text += '<textarea style="max-width: 480px; width: 480px; height:300px; max-height:300px; overflow-y: auto" class="configStringInput" autofocus required id="addValueDialog_input"></textarea>';
-    } else {
-        text += '<input style="width: 480px" class="configStringInput" autofocus required id="addValueDialog_input"/>';
-    }
-
-    var inputFunction = function() {
-        PWM_MAIN.getObject('dialog_ok_button').disabled = true;
-        PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'hidden';
-
-        var value = PWM_MAIN.getObject('addValueDialog_input').value;
-        if (value.length > 0) {
-            var passedValidation = regexObject  != null && regexObject.test(value);
-
-            if (passedValidation) {
-                PWM_MAIN.getObject('dialog_ok_button').disabled = false;
-                PWM_VAR['temp-dialogInputValue'] = PWM_MAIN.getObject('addValueDialog_input').value;
-            } else {
-                PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'visible';
-            }
-        }
-    };
-
-    var okFunction = function() {
-        var value = PWM_VAR['temp-dialogInputValue'];
-        completeFunction(value);
-    };
-
-    PWM_MAIN.showDialog({
-        title:title,
-        text:text,
-        okAction:okFunction,
-        showCancel:true,
-        showClose: true,
-        allowMove: true,
-        loadFunction:function(){
-            PWM_MAIN.getObject('addValueDialog_input').value = initialValue;
-            if (regexString && regexString.length > 1) {
-                PWM_MAIN.getObject('addValueDialog_input').setAttribute('pattern',regexString);
-            }
-            if (placeholder && placeholder.length > 1) {
-                PWM_MAIN.getObject('addValueDialog_input').setAttribute('placeholder',placeholder);
-            }
-            inputFunction();
-            PWM_MAIN.addEventHandler('addValueDialog_input','input',function(){
-                inputFunction();
-            });
-        }
-    });
-};
-
-UILibrary.addTextValueToElement = function(elementID, input) {
-    var element = PWM_MAIN.getObject(elementID);
-    if (element) {
-        element.innerHTML = '';
-        element.appendChild(document.createTextNode(input));
-    }
-};
-
-UILibrary.addAddLocaleButtonRow = function(parentDiv, keyName, addFunction, existingLocales) {
-    var availableLocales = PWM_GLOBAL['localeInfo'];
-
-    var tableRowElement = document.createElement('tr');
-    tableRowElement.setAttribute("style","border-width: 0");
-
-    var bodyHtml = '';
-    bodyHtml += '<td style="border-width: 0" colspan="5">';
-    bodyHtml += '<select id="' + keyName + '-addLocaleValue">';
-
-    var localesAdded = 0;
-    for (var localeIter in availableLocales) {
-        if (localeIter != PWM_GLOBAL['defaultLocale']) {
-            if (!existingLocales || (existingLocales.indexOf(localeIter) == -1)) {
-                localesAdded++;
-                var labelText = availableLocales[localeIter] + " (" + localeIter + ")";
-                bodyHtml += '<option value="' + localeIter + '">' + labelText + '</option>';
-            }
-        }
-    }
-    bodyHtml += '</select>';
-
-    bodyHtml += '<button type="button" class="btn" id="' + keyName + '-addLocaleButton"><span class="btn-icon fa fa-plus-square"></span>Add Locale</button>'
-
-    bodyHtml += '</td>';
-    if (localesAdded == 0) {
-        bodyHtml = '<td style="border-width: 0" colspan="5"><span class="footnote">All locales present</span></td>';
-    }
-    tableRowElement.innerHTML = bodyHtml;
-    PWM_MAIN.getObject(parentDiv).appendChild(tableRowElement);
-
-    PWM_MAIN.addEventHandler(keyName + '-addLocaleButton','click',function(){
-        var value = PWM_MAIN.getObject(keyName + "-addLocaleValue").value;
-        addFunction(value);
-    });
-};
-
-UILibrary.manageNumericInput = function(elementID, readFunction) {
-    var element = PWM_MAIN.getObject(elementID);
-    if (!element) {
-        return;
-    }
-    var validChecker = function(value) {
-        if (!value) {
-            return false;
-        }
-        if (value.match('^[0-9]*$') == null) {
-            return false;
-        }
-        if (element.hasAttribute('min')) {
-            if (value < parseInt(element.getAttribute('min'))) {
-                return false;
-            }
-        }
-        if (element.hasAttribute('max')) {
-            if (value > parseInt(element.getAttribute('max'))) {
-                return false;
-            }
-        }
-        return true;
-    };
-    PWM_MAIN.addEventHandler(elementID,'input',function(){
-        var value = element.value;
-        if (validChecker(value)) {
-            console.log('valid numerical input value: ' + value);
-            readFunction(value);
-        } else {
-            console.log('invalid numerical input value: ' + value);
-        }
-    });
-};
-
-ActionHandler.showHeadersDialog = function(keyName, iteration) {
-    var addValue = function(keyName, iteration) {
-        var body = '<table class="noborder">';
-        body += '<tr><td>Name</td><td><input class="configStringInput" id="newHeaderName" style="width:300px"/></td></tr>';
-        body += '<tr><td>Value</td><td><input class="configStringInput" id="newHeaderValue" style="width:300px"/></td></tr>';
-        body += '</table>';
-
-        var updateFunction = function(){
-            PWM_MAIN.getObject('dialog_ok_button').disabled = true;
-            PWM_VAR['newHeaderName'] = PWM_MAIN.getObject('newHeaderName').value;
-            PWM_VAR['newHeaderValue'] = PWM_MAIN.getObject('newHeaderValue').value;
-            if (PWM_VAR['newHeaderName'].length > 0 && PWM_VAR['newHeaderValue'].length > 0) {
-                PWM_MAIN.getObject('dialog_ok_button').disabled = false;
-            }
-        };
-
-        PWM_MAIN.showConfirmDialog({
-            title:'New Header',
-            text:body,
-            showClose:true,
-            loadFunction:function(){
-                PWM_MAIN.addEventHandler('newHeaderName','input',function(){
-                    updateFunction();
-                });
-                PWM_MAIN.addEventHandler('newHeaderValue','input',function(){
-                    updateFunction();
-                });
-                updateFunction();
-            },okAction:function(){
-                var headers = PWM_VAR['clientSettingCache'][keyName][iteration]['headers'];
-                headers[PWM_VAR['newHeaderName']] = PWM_VAR['newHeaderValue'];
-                ActionHandler.write(keyName);
-                ActionHandler.showHeadersDialog(keyName, iteration);
-            },cancelAction:function(){
-                ActionHandler.showHeadersDialog(keyName, iteration);
-            }
-        });
-
-    };
-
-    var settingValue = PWM_VAR['clientSettingCache'][keyName][iteration];
-    require(["dijit/Dialog","dijit/form/ValidationTextBox","dijit/form/Button","dijit/form/TextBox"],function(Dialog,ValidationTextBox,Button,TextBox){
-        var inputID = 'value_' + keyName + '_' + iteration + "_" + "headers_";
-
-        var bodyText = '';
-        bodyText += '<table class="noborder">';
-        bodyText += '<tr><td><b>Name</b></td><td><b>Value</b></td></tr>';
-        for (var iter in settingValue['headers']) {
-            (function(headerName) {
-                var value = settingValue['headers'][headerName];
-                var optionID = inputID + headerName;
-                bodyText += '<tr><td class="border">' + headerName + '</td><td class="border">' + value + '</td>';
-                bodyText += '<td style="width:15px;"><span class="delete-row-icon action-icon fa fa-times" id="button-' + optionID + '-deleteRow"></span></td>';
-                bodyText += '</tr>';
-            }(iter));
-        }
-        bodyText += '</table>';
-
-        PWM_MAIN.showDialog({
-            title: 'HTTP Headers for webservice ' + settingValue['name'],
-            text: bodyText,
-            buttonHtml:'<button id="button-' + inputID + '-addHeader" class="btn"><span class="btn-icon fa fa-plus-square"></span>Add Header</button>',
-            okAction: function() {
-                ActionHandler.showOptionsDialog(keyName,iteration);
-            },
-            loadFunction: function() {
-                for (var iter in settingValue['headers']) {
-                    (function(headerName) {
-                        var headerID = inputID + headerName;
-                        PWM_MAIN.addEventHandler('button-' + headerID + '-deleteRow', 'click', function () {
-                            delete settingValue['headers'][headerName];
-                            ActionHandler.write(keyName);
-                            ActionHandler.showHeadersDialog(keyName, iteration);
-                        });
-                    }(iter));
-                }
-                PWM_MAIN.addEventHandler('button-' + inputID + '-addHeader','click',function(){
-                    ActionHandler.addHeader(keyName, iteration);
-                });
-            }
-        });
-    });
-};
-

+ 4 - 0
pwm/servlet/web/public/resources/js/configeditor.js

@@ -1318,3 +1318,7 @@ PWM_CFGEDIT.openMenuPanel = function() {
     PWM_MAIN.setStyle('header-warning','display','inherit');
     PWM_MAIN.setStyle('button-openMenu','display','none');
 };
+
+
+
+

+ 7 - 7
pwm/servlet/web/public/resources/js/configguide.js

@@ -28,7 +28,7 @@ var PWM_GLOBAL = PWM_GLOBAL || {};
 
 PWM_GUIDE.selectTemplate = function(template) {
     PWM_MAIN.showWaitDialog({title:'Loading...',loadFunction:function() {
-        var url = "ConfigGuide?processAction=selectTemplate&template=" + template;
+        var url = "config-guide?processAction=selectTemplate&template=" + template;
         PWM_MAIN.showDialog(url,function(result){
             if (!result['error']) {
                 PWM_MAIN.getObject('button_next').disabled = template == "NOTSELECTED";
@@ -45,7 +45,7 @@ PWM_GUIDE.updateForm = function() {
     require(["dojo","dijit/registry","dojo/dom-form"],function(dojo,registry,domForm){
         var formJson = dojo.formToJson('configForm');
         dojo.xhrPost({
-            url: "ConfigGuide?processAction=updateForm&pwmFormID=" + PWM_GLOBAL['pwmFormID'],
+            url: "config-guide?processAction=updateForm&pwmFormID=" + PWM_GLOBAL['pwmFormID'],
             postData: formJson,
             headers: {"Accept":"application/json"},
             contentType: "application/json;charset=utf-8",
@@ -67,7 +67,7 @@ PWM_GUIDE.gotoStep = function(step) {
     PWM_MAIN.showWaitDialog({loadFunction:function(){
         //preload in case of server restart
         PWM_MAIN.preloadAll(function(){
-            var url =  "ConfigGuide?processAction=gotoStep&step=" + step;
+            var url =  "config-guide?processAction=gotoStep&step=" + step;
             var loadFunction = function(result) {
                 if (result['error']) {
                     PWM_MAIN.showErrorDialog(result);
@@ -78,7 +78,7 @@ PWM_GUIDE.gotoStep = function(step) {
                         return;
                     }
                 }
-                PWM_MAIN.goto('ConfigGuide');
+                PWM_MAIN.goto('config-guide');
             };
             PWM_MAIN.ajaxRequest(url,loadFunction);
         });
@@ -87,10 +87,10 @@ PWM_GUIDE.gotoStep = function(step) {
 
 PWM_GUIDE.setUseConfiguredCerts = function(value) {
     PWM_MAIN.showWaitDialog({title:'Loading...',loadFunction:function() {
-        var url = "ConfigGuide?processAction=useConfiguredCerts&value=" + value;
+        var url = "config-guide?processAction=useConfiguredCerts&value=" + value;
         var loadFunction = function(result) {
             if (!result['error']) {
-                PWM_MAIN.goto("ConfigGuide");
+                PWM_MAIN.goto("config-guide");
             } else {
                 PWM_MAIN.showError(result['errorDetail']);
             }
@@ -102,7 +102,7 @@ PWM_GUIDE.setUseConfiguredCerts = function(value) {
 PWM_GUIDE.extendSchema = function() {
     PWM_MAIN.showConfirmDialog({text:"Are you sure you want to extend the LDAP schema?",okAction:function(){
         PWM_MAIN.showWaitDialog({loadFunction:function() {
-            var url = "ConfigGuide?processAction=extendSchema";
+            var url = "config-guide?processAction=extendSchema";
             var loadFunction = function(result) {
                 if (result['error']) {
                     PWM_MAIN.showError(result['errorDetail']);

+ 298 - 0
pwm/servlet/web/public/resources/js/uilibrary.js

@@ -0,0 +1,298 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://code.google.com/p/pwm/
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2015 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
+ */
+
+// -------------------------- common elements handler ------------------------------------
+
+var UILibrary = {};
+UILibrary.stringEditorDialog = function(options){
+    options = options === undefined ? {} : options;
+    var title = 'title' in options ? options['title'] : 'Edit Value';
+    var completeFunction = 'completeFunction' in options ? options['completeFunction'] : function() {alert('no string editor dialog complete function')};
+    var regexString = 'regex' in options && options['regex'] ? options['regex'] : '.+';
+    var initialValue = 'value' in options ? options['value'] : '';
+    var placeholder = 'placeholder' in options ? options['placeholder'] : '';
+    var textarea = 'textarea' in options ? options['textarea'] : false;
+
+    var regexObject = new RegExp(regexString);
+    var text = '';
+    text += '<div style="visibility: hidden;" id="panel-valueWarning"><span class="fa fa-warning message-error"></span>&nbsp;' + PWM_CONFIG.showString('Warning_ValueIncorrectFormat') + '</div>';
+    text += '<br/>';
+
+    if (textarea) {
+        text += '<textarea style="max-width: 480px; width: 480px; height:300px; max-height:300px; overflow-y: auto" class="configStringInput" autofocus required id="addValueDialog_input"></textarea>';
+    } else {
+        text += '<input style="width: 480px" class="configStringInput" autofocus required id="addValueDialog_input"/>';
+    }
+
+    var inputFunction = function() {
+        PWM_MAIN.getObject('dialog_ok_button').disabled = true;
+        PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'hidden';
+
+        var value = PWM_MAIN.getObject('addValueDialog_input').value;
+        if (value.length > 0) {
+            var passedValidation = regexObject  != null && regexObject.test(value);
+
+            if (passedValidation) {
+                PWM_MAIN.getObject('dialog_ok_button').disabled = false;
+                PWM_VAR['temp-dialogInputValue'] = PWM_MAIN.getObject('addValueDialog_input').value;
+            } else {
+                PWM_MAIN.getObject('panel-valueWarning').style.visibility = 'visible';
+            }
+        }
+    };
+
+    var okFunction = function() {
+        var value = PWM_VAR['temp-dialogInputValue'];
+        completeFunction(value);
+    };
+
+    PWM_MAIN.showDialog({
+        title:title,
+        text:text,
+        okAction:okFunction,
+        showCancel:true,
+        showClose: true,
+        allowMove: true,
+        dialogClass: 'auto',
+        loadFunction:function(){
+            PWM_MAIN.getObject('addValueDialog_input').value = initialValue;
+            if (regexString && regexString.length > 1) {
+                PWM_MAIN.getObject('addValueDialog_input').setAttribute('pattern',regexString);
+            }
+            if (placeholder && placeholder.length > 1) {
+                PWM_MAIN.getObject('addValueDialog_input').setAttribute('placeholder',placeholder);
+            }
+            inputFunction();
+            PWM_MAIN.addEventHandler('addValueDialog_input','input',function(){
+                inputFunction();
+            });
+        }
+    });
+};
+
+UILibrary.addTextValueToElement = function(elementID, input) {
+    var element = PWM_MAIN.getObject(elementID);
+    if (element) {
+        element.innerHTML = '';
+        element.appendChild(document.createTextNode(input));
+    }
+};
+
+UILibrary.addAddLocaleButtonRow = function(parentDiv, keyName, addFunction, existingLocales) {
+    var availableLocales = PWM_GLOBAL['localeInfo'];
+
+    var tableRowElement = document.createElement('tr');
+    tableRowElement.setAttribute("style","border-width: 0");
+
+    var bodyHtml = '';
+    bodyHtml += '<td style="border-width: 0" colspan="5">';
+    bodyHtml += '<select id="' + keyName + '-addLocaleValue">';
+
+    var localesAdded = 0;
+    for (var localeIter in availableLocales) {
+        if (localeIter != PWM_GLOBAL['defaultLocale']) {
+            if (!existingLocales || (existingLocales.indexOf(localeIter) == -1)) {
+                localesAdded++;
+                var labelText = availableLocales[localeIter] + " (" + localeIter + ")";
+                bodyHtml += '<option value="' + localeIter + '">' + labelText + '</option>';
+            }
+        }
+    }
+    bodyHtml += '</select>';
+
+    bodyHtml += '<button type="button" class="btn" id="' + keyName + '-addLocaleButton"><span class="btn-icon fa fa-plus-square"></span>Add Locale</button>'
+
+    bodyHtml += '</td>';
+    if (localesAdded == 0) {
+        bodyHtml = '<td style="border-width: 0" colspan="5"><span class="footnote">All locales present</span></td>';
+    }
+    tableRowElement.innerHTML = bodyHtml;
+    PWM_MAIN.getObject(parentDiv).appendChild(tableRowElement);
+
+    PWM_MAIN.addEventHandler(keyName + '-addLocaleButton','click',function(){
+        var value = PWM_MAIN.getObject(keyName + "-addLocaleValue").value;
+        addFunction(value);
+    });
+};
+
+UILibrary.manageNumericInput = function(elementID, readFunction) {
+    var element = PWM_MAIN.getObject(elementID);
+    if (!element) {
+        return;
+    }
+    var validChecker = function(value) {
+        if (!value) {
+            return false;
+        }
+        if (value.match('^[0-9]*$') == null) {
+            return false;
+        }
+        if (element.hasAttribute('min')) {
+            if (value < parseInt(element.getAttribute('min'))) {
+                return false;
+            }
+        }
+        if (element.hasAttribute('max')) {
+            if (value > parseInt(element.getAttribute('max'))) {
+                return false;
+            }
+        }
+        return true;
+    };
+    PWM_MAIN.addEventHandler(elementID,'input',function(){
+        var value = element.value;
+        if (validChecker(value)) {
+            console.log('valid numerical input value: ' + value);
+            readFunction(value);
+        } else {
+            console.log('invalid numerical input value: ' + value);
+        }
+    });
+};
+
+UILibrary.editLdapDN = function(nextFunction, options) {
+    options = options === undefined ? {} : options;
+    var profile = 'profile' in options ? options['profile'] : '';
+    var currentDN = 'currentDN' in options ? options['currentDN'] : '';
+    var processResults = function(data) {
+        var body = '';
+        if (data['error']) {
+            body += '<div>Unable to browse LDAP directory: ' + data['errorMessage'] + '</div>';
+            if (data['errorDetail']) {
+                body += '<br/><div>' + data['errorDetail'] + '</div>';
+            }
+            PWM_MAIN.showDialog({title:'Error',text:body,okAction:function(){
+                UILibrary.stringEditorDialog({value:currentDN,completeFunction:nextFunction});
+            }});
+            return;
+        }
+
+        if (!PWM_MAIN.isEmpty(data['data']['profileList'])) {
+            body += '<div style="text-align: center">LDAP Profile <select id="select-profileList"></select></div><br/>';
+        }
+        body += '<div style="text-align: center">';
+        if (currentDN && currentDN.length > 0 ) {
+            body += currentDN;
+        } else {
+            body += '[root]';
+        }
+        body += '</div><br/>';
+
+        body += '<div style="min-width:500px; max-height: 400px; overflow-y: auto; overflow-x:hidden; white-space: nowrap">';
+        body += '<table class="noborder">';
+        if ('parentDN' in data['data']) {
+            var parentDN = data['data']['parentDN'];
+            body += '<tr><td style="width:10px" class="navigableDN" data-dn="' + parentDN + '"><span class="fa fa-level-up"></span></td>';
+            body += '<td title="' + parentDN + '">[parent]</td>';
+            body += '</td>';
+            body += '</tr>';
+        }
+
+        var makeEntryHtml = function(dnInformation,navigable) {
+            var loopDN = dnInformation['dn'];
+            var entryName = dnInformation['entryName'];
+            var out = '';
+            if (navigable) {
+                out += '<tr><td style="width:10px" class="navigableDN" data-dn="' + loopDN + '"><span class="fa fa-level-down"></span></td>';
+            } else {
+                out += '<tr><td style="width:10px"></td>';
+            }
+            out += '<td class="selectableDN" data-dn="' + loopDN + '" title="' + loopDN + '"><a><code>' + entryName + '</code></a></td>';
+            out += '</tr>';
+            return out;
+        };
+
+        if (data['data']['navigableDNlist'] && !PWM_MAIN.isEmpty(data['data']['navigableDNlist'])) {
+            var navigableDNlist = data['data']['navigableDNlist'];
+            for (var i in navigableDNlist) {
+                body += makeEntryHtml(navigableDNlist[i],true);
+            }
+        }
+        if (data['data']['selectableDNlist'] && !PWM_MAIN.isEmpty(data['data']['selectableDNlist'])) {
+            var selectableDNlist = data['data']['selectableDNlist'];
+            for (var i in selectableDNlist) {
+                body += makeEntryHtml(selectableDNlist[i],false);
+            }
+        }
+        body += '</table></div>';
+
+        if (data['data']['maxResults']) {
+            body += '<div class="footnote">' + PWM_MAIN.showString('Display_SearchResultsExceeded') + '</div>';
+        }
+
+        body += '<div class="buttonbar"><button class="btn" id="button-editDN"><span class="btn-icon fa fa-edit"></span>Edit Text</button>';
+        body += '<button class="btn" id="button-clearDN"><span class="btn-icon fa fa-times"></span>Clear Value</button></div>';
+
+        PWM_MAIN.showDialog({title:'LDAP Browser',dialogClass:'auto',showOk:false,showClose:true,text:body,loadFunction:function(){
+            PWM_MAIN.addEventHandler('button-editDN','click',function(){
+                UILibrary.stringEditorDialog({value:currentDN,completeFunction:nextFunction});
+            });
+            PWM_MAIN.addEventHandler('button-clearDN','click',function(){
+                nextFunction('');
+                PWM_MAIN.closeWaitDialog();
+            });
+
+            PWM_MAIN.doQuery(".selectableDN",function(element){
+                var dnValue = element.getAttribute("data-dn");
+                PWM_MAIN.addEventHandler(element,'click',function(){
+                    nextFunction(dnValue);
+                    PWM_MAIN.closeWaitDialog();
+                });
+            });
+
+            PWM_MAIN.doQuery(".navigableDN",function(element){
+                var dnValue = element.getAttribute("data-dn");
+                PWM_MAIN.addEventHandler(element,'click',function(){
+                    UILibrary.editLdapDN(nextFunction,{profile:profile,currentDN:dnValue});
+                });
+            });
+
+            if (!PWM_MAIN.isEmpty(data['data']['profileList'])) {
+                var profileList = data['data']['profileList'];
+                var profileSelect = PWM_MAIN.getObject('select-profileList');
+                for (var i in profileList) {
+                    (function(loopProfile) {
+                        var optionElement = document.createElement('option');
+                        optionElement.innerHTML = loopProfile;
+                        optionElement.value = loopProfile;
+                        if (loopProfile == profile) {
+                            optionElement.selected = true;
+                        }
+                        profileSelect.appendChild(optionElement);
+                    })(profileList[i]);
+                }
+                PWM_MAIN.addEventHandler('select-profileList','change',function(){
+                    var value = profileSelect.options[profileSelect.selectedIndex].value;
+                    UILibrary.editLdapDN(nextFunction,{profile:value,currentDN:''});
+                });
+            }
+        }});
+    };
+
+    PWM_MAIN.showWaitDialog({loadFunction:function(){
+        var content = {};
+        content['profile'] = profile;
+        content['dn'] = currentDN;
+        var url = window.location.pathname + "?processAction=browseLdap";
+        PWM_MAIN.ajaxRequest(url,processResults,{content:content});
+    }});
+};

+ 1 - 0
pwm/servlet/web/public/resources/style.css

@@ -694,6 +694,7 @@ progress[value] {
 .dialogBody { width: 500px; max-width: 500px; }
 .dialogBody.narrow { width: 350px; max-width: 350px; }
 .dialogBody.wide { width: 95%; max-width: 100%; min-width: 800px; }
+.dialogBody.auto { width: auto }
 .WaitDialogBlank { height: 46px; width: 46px; margin-left: auto; margin-right: auto; background-image: url('wait.gif');}
 #dialogPopup_underlay { background-color: #222222; }
 #idleDialog_underlay { background-color: #111111; }