Browse Source

resolve configured ldap DNs cannonically

Jason Rivard 8 years ago
parent
commit
1114dd8de0

+ 2 - 2
src/main/java/password/pwm/AppProperty.java

@@ -250,8 +250,8 @@ public enum     AppProperty {
     SECURITY_SHAREDHISTORY_CASE_INSENSITIVE         ("security.sharedHistory.caseInsensitive"),
     SECURITY_SHAREDHISTORY_SALT_LENGTH              ("security.sharedHistory.saltLength"),
     SECURITY_CERTIFICATES_VALIDATE_TIMESTAMPS       ("security.certs.validateTimestamps"),
-    SECURITY_LDAP_BASEDN_RESOLVE_CANONICAL_DN       ("security.ldap.resolveCanonicalDN"),
-    SECURITY_LDAP_BASEDN_CANONICAL_CACHE_SECONDS    ("security.ldap.canonicalCacheSeconds"),
+    SECURITY_LDAP_RESOLVE_CANONICAL_DN              ("security.ldap.resolveCanonicalDN"),
+    SECURITY_LDAP_CANONICAL_CACHE_SECONDS           ("security.ldap.canonicalCacheSeconds"),
     SECURITY_CONFIG_MIN_SECURITY_KEY_LENGTH         ("security.config.minSecurityKeyLength"),
     SECURITY_DEFAULT_EPHEMERAL_BLOCK_ALG            ("security.defaultEphemeralBlockAlg"),
     SECURITY_DEFAULT_EPHEMERAL_HASH_ALG             ("security.defaultEphemeralHashAlg"),

+ 84 - 3
src/main/java/password/pwm/config/profile/LdapProfile.java

@@ -22,7 +22,12 @@
 
 package password.pwm.config.profile;
 
+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 password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.config.PwmSetting;
 import password.pwm.config.PwmSettingCategory;
@@ -30,13 +35,24 @@ import password.pwm.config.StoredValue;
 import password.pwm.config.UserPermission;
 import password.pwm.config.stored.StoredConfigurationImpl;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.LdapPermissionTester;
+import password.pwm.svc.cache.CacheKey;
+import password.pwm.svc.cache.CachePolicy;
 import password.pwm.util.java.StringUtil;
+import password.pwm.util.java.TimeDuration;
+import password.pwm.util.logging.PwmLogger;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 public class LdapProfile extends AbstractProfile implements Profile {
+    private static final PwmLogger LOGGER = PwmLogger.forClass(LdapProfile.class);
+
     protected LdapProfile(final String identifier, final Map<PwmSetting, StoredValue> storedValueMap) {
         super(identifier, storedValueMap);
     }
@@ -46,9 +62,34 @@ public class LdapProfile extends AbstractProfile implements Profile {
         return new LdapProfile(profileID, valueMap);
     }
 
-    public Map<String, String> getLoginContexts() {
-        final List<String> values = readSettingAsStringArray(PwmSetting.LDAP_LOGIN_CONTEXTS);
-        return StringUtil.convertStringListToNameValuePair(values, ":::");
+    public Map<String, String> getSelectableContexts(
+            final PwmApplication pwmApplication
+    )
+            throws PwmUnrecoverableException
+    {
+        final List<String> rawValues = readSettingAsStringArray(PwmSetting.LDAP_LOGIN_CONTEXTS);
+        final Map<String, String> configuredValues = StringUtil.convertStringListToNameValuePair(rawValues, ":::");
+        final Map<String, String> canonicalValues = new LinkedHashMap<>();
+        for (final String dn : configuredValues.keySet() ) {
+            final String label = configuredValues.get(dn);
+            final String canonicalDN = readCanonicalDN(pwmApplication, dn);
+            canonicalValues.put(canonicalDN, label);
+        }
+        return Collections.unmodifiableMap(canonicalValues);
+    }
+
+    public List<String> getRootContexts(
+            final PwmApplication pwmApplication
+    )
+            throws PwmUnrecoverableException
+    {
+        final List<String> rawValues = readSettingAsStringArray(PwmSetting.LDAP_CONTEXTLESS_ROOT);
+        final List<String> canonicalValues = new ArrayList<>();
+        for (final String dn : rawValues ) {
+            final String canonicalDN = readCanonicalDN(pwmApplication, dn);
+            canonicalValues.add(canonicalDN);
+        }
+        return Collections.unmodifiableList(canonicalValues);
     }
 
     @Override
@@ -76,4 +117,44 @@ public class LdapProfile extends AbstractProfile implements Profile {
     public List<UserPermission> getPermissionMatches() {
         throw new UnsupportedOperationException();
     }
+
+    public String readCanonicalDN(
+            final PwmApplication pwmApplication,
+            final String dnValue
+    )
+            throws PwmUnrecoverableException
+    {
+        {
+            final boolean doCanonicalDnResolve = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_LDAP_RESOLVE_CANONICAL_DN));
+            if (!doCanonicalDnResolve) {
+                return dnValue;
+            }
+        }
+
+        String canonicalValue = null;
+        final CacheKey cacheKey = CacheKey.makeCacheKey(LdapPermissionTester.class, null, "canonicalDN-" + this.getIdentifier() + "-" + dnValue);
+        {
+            final String cachedDN = pwmApplication.getCacheService().get(cacheKey);
+            if (cachedDN != null) {
+                canonicalValue = cachedDN;
+            }
+        }
+
+        if (canonicalValue == null) {
+            try {
+                final ChaiProvider chaiProvider = this.getProxyChaiProvider(pwmApplication);
+                final ChaiEntry chaiEntry = ChaiFactory.createChaiEntry(dnValue, chaiProvider);
+                canonicalValue = chaiEntry.readCanonicalDN();
+                final long cacheSeconds = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_LDAP_CANONICAL_CACHE_SECONDS));
+                final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration(new TimeDuration(cacheSeconds, TimeUnit.SECONDS));
+                pwmApplication.getCacheService().put(cacheKey, cachePolicy, canonicalValue);
+                LOGGER.trace("read and cached canonical ldap DN value for input '" + dnValue + "' as '" + canonicalValue + "'");
+            } catch (ChaiUnavailableException | ChaiOperationException e) {
+                LOGGER.error("error while reading canonicalDN for dn value '" + dnValue + "', error: " + e.getMessage());
+                return dnValue;
+            }
+        }
+
+        return canonicalValue;
+    }
 }

+ 5 - 2
src/main/java/password/pwm/config/value/VerificationMethodValue.java

@@ -29,8 +29,8 @@ import password.pwm.config.StoredValue;
 import password.pwm.config.option.IdentityVerificationMethod;
 import password.pwm.error.PwmOperationalException;
 import password.pwm.i18n.Display;
-import password.pwm.util.java.JsonUtil;
 import password.pwm.util.LocaleHelper;
+import password.pwm.util.java.JsonUtil;
 import password.pwm.util.logging.PwmLogger;
 import password.pwm.util.secure.PwmSecurityKey;
 
@@ -38,6 +38,7 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -67,7 +68,9 @@ public class VerificationMethodValue extends AbstractValue implements StoredValu
         }
 
         public Map<IdentityVerificationMethod, VerificationMethodSetting> getMethodSettings() {
-            return Collections.unmodifiableMap(methodSettings);
+            final Map<IdentityVerificationMethod, VerificationMethodSetting> tempMap = new LinkedHashMap<>(methodSettings);
+            tempMap.remove(null);
+            return Collections.unmodifiableMap(tempMap);
         }
 
         public int getMinOptionalRequired() {

+ 11 - 4
src/main/java/password/pwm/http/filter/ConfigAccessFilter.java

@@ -116,7 +116,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
 
             if (!pwmRequest.getPwmSession().getSessionManager().checkPermission(pwmRequest.getPwmApplication(), Permission.PWMADMIN)) {
                 final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNAUTHORIZED);
-                pwmRequest.respondWithError(errorInformation);
+                denyAndError(pwmRequest, errorInformation);
                 return ProcessStatus.Halt;
             }
         }
@@ -132,8 +132,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
         if (!storedConfig.hasPassword()) {
             final String errorMsg = "config file does not have a configuration password";
             final ErrorInformation errorInformation = new ErrorInformation(PwmError.CONFIG_FORMAT_ERROR,errorMsg,new String[]{errorMsg});
-            pwmRequest.respondWithError(errorInformation, true);
-            return ProcessStatus.Halt;
+            return denyAndError(pwmRequest, errorInformation);
         }
 
         if (configManagerBean.isPasswordVerified()) {
@@ -204,7 +203,7 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
                     pwmApplication.getIntruderManager().mark(RecordType.USERNAME, PwmConstants.CONFIGMANAGER_INTRUDER_USERNAME, pwmSession.getLabel());
                     final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_PASSWORD_ONLY_BAD);
                     updateLoginHistory(pwmRequest,pwmRequest.getUserInfoIfLoggedIn(), false);
-                    throw new PwmUnrecoverableException(errorInformation);
+                    return denyAndError(pwmRequest, errorInformation);
                 }
             }
         }
@@ -394,4 +393,12 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
             throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg));
         }
     }
+
+    private static ProcessStatus denyAndError(final PwmRequest pwmRequest, final ErrorInformation errorInformation)
+            throws ServletException, PwmUnrecoverableException, IOException
+    {
+        pwmRequest.setAttribute(PwmRequest.Attribute.PwmErrorInfo, errorInformation);
+        forwardToJsp(pwmRequest);
+        return ProcessStatus.Halt;
+    }
 }

+ 2 - 2
src/main/java/password/pwm/http/servlet/ControlledPwmServlet.java

@@ -103,11 +103,11 @@ public abstract class ControlledPwmServlet extends AbstractPwmServlet implements
         } catch (InvocationTargetException e) {
             final Throwable cause = e.getCause();
             if (cause != null) {
-                final String msg = "unexpected error during action handler for '" + action + "', error: " + e.getMessage();
-                LOGGER.error(msg, cause);
                 if (cause instanceof PwmUnrecoverableException) {
                     throw (PwmUnrecoverableException) cause;
                 }
+                final String msg = "unexpected error during action handler for '" + action + "', error: " + cause.getMessage();
+                LOGGER.error(msg);
                 throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN, msg));
             }
             LOGGER.error("uncaused invocation error: " + e.getMessage(),e);

+ 4 - 44
src/main/java/password/pwm/ldap/LdapPermissionTester.java

@@ -22,14 +22,9 @@
 
 package password.pwm.ldap;
 
-import com.novell.ldapchai.ChaiEntry;
-import com.novell.ldapchai.ChaiFactory;
 import com.novell.ldapchai.ChaiUser;
 import com.novell.ldapchai.exception.ChaiException;
-import com.novell.ldapchai.exception.ChaiOperationException;
-import com.novell.ldapchai.exception.ChaiUnavailableException;
 import com.novell.ldapchai.provider.ChaiProvider;
-import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.PwmConstants;
 import password.pwm.bean.SessionLabel;
@@ -43,8 +38,6 @@ import password.pwm.error.PwmOperationalException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.ldap.search.SearchConfiguration;
 import password.pwm.ldap.search.UserSearchEngine;
-import password.pwm.svc.cache.CacheKey;
-import password.pwm.svc.cache.CachePolicy;
 import password.pwm.util.logging.PwmLogger;
 
 import java.util.Collections;
@@ -256,51 +249,18 @@ public class LdapPermissionTester {
     private static boolean testUserDNmatch(
             final PwmApplication pwmApplication,
             final SessionLabel sessionLabel,
-            final String ldapBase,
+            final String baseDN,
             final UserIdentity userIdentity
     )
             throws PwmUnrecoverableException
     {
-        if (ldapBase == null || ldapBase.trim().isEmpty()) {
+        if (baseDN == null || baseDN.trim().isEmpty()) {
             return true;
         }
 
-        final String permissionBase = ldapBase.trim();
+        final LdapProfile ldapProfile = userIdentity.getLdapProfile(pwmApplication.getConfig());
+        final String canonicalBaseDN = ldapProfile.readCanonicalDN(pwmApplication, baseDN);
         final String userDN = userIdentity.getUserDN();
-        if (userDN.endsWith(permissionBase)) {
-            return true;
-        }
-
-        {
-            final boolean doCanonicalDnResolve = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_LDAP_BASEDN_RESOLVE_CANONICAL_DN));
-            if (!doCanonicalDnResolve) {
-                return false;
-            }
-        }
-
-        String canonicalBaseDN = null;
-        final CacheKey cacheKey = CacheKey.makeCacheKey(LdapPermissionTester.class, null, "canonicalDN-" + userIdentity.getLdapProfileID() + "-" + ldapBase);
-        {
-            final String cachedDN = pwmApplication.getCacheService().get(cacheKey);
-            if (cachedDN != null) {
-                canonicalBaseDN = cachedDN;
-            }
-        }
-
-        if (canonicalBaseDN == null) {
-            try {
-                final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID());
-                final ChaiEntry chaiEntry = ChaiFactory.createChaiEntry(ldapBase, chaiProvider);
-                canonicalBaseDN = chaiEntry.readCanonicalDN();
-                final long cacheSeconds = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_LDAP_BASEDN_CANONICAL_CACHE_SECONDS));
-                final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpirationMS(cacheSeconds * 1000);
-                pwmApplication.getCacheService().put(cacheKey, cachePolicy, canonicalBaseDN);
-            } catch (ChaiUnavailableException | ChaiOperationException e) {
-                LOGGER.error(sessionLabel, "error while testing userDN match against baseDN '" + ldapBase + "', error: " + e.getMessage());
-                return false;
-            }
-        }
-
         return userDN.endsWith(canonicalBaseDN);
     }
 }

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

@@ -373,7 +373,7 @@ public class UserSearchEngine implements PwmService {
                 }
             }
         } else {
-            searchContexts = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_CONTEXTLESS_ROOT);
+            searchContexts = ldapProfile.getRootContexts(pwmApplication);
         }
 
         final long timeLimitMS = searchConfiguration.getSearchTimeout() != 0
@@ -460,15 +460,15 @@ public class UserSearchEngine implements PwmService {
         return returnMap;
     }
 
-    private static void validateSpecifiedContext(final LdapProfile profile, final String context)
-            throws PwmOperationalException
+    private void validateSpecifiedContext(final LdapProfile profile, final String context)
+            throws PwmOperationalException, PwmUnrecoverableException
     {
-        if (profile.getLoginContexts() == null || profile.getLoginContexts().isEmpty()) {
+        final Map<String,String> selectableContexts = profile.getSelectableContexts(pwmApplication);
+        if (selectableContexts == null || selectableContexts.isEmpty()) {
             throw new PwmOperationalException(PwmError.ERROR_UNKNOWN,"context specified, but no selectable contexts are configured");
         }
-        final Collection<String> loginContexts = profile.getLoginContexts().keySet();
 
-        for (final String loopContext : loginContexts) {
+        for (final String loopContext : selectableContexts.keySet()) {
             if (loopContext.equals(context)) {
                 return;
             }

+ 1 - 1
src/main/java/password/pwm/ws/server/rest/RestAppDataServer.java

@@ -457,7 +457,7 @@ public class RestAppDataServer extends AbstractRestServer {
         if (pwmApplication.getConfig().readSettingAsEnum(PwmSetting.LDAP_SELECTABLE_CONTEXT_MODE, SelectableContextMode.class) != SelectableContextMode.NONE) {
             final Map<String,Map<String,String>> ldapProfiles = new LinkedHashMap<>();
             for (final String ldapProfile : pwmApplication.getConfig().getLdapProfiles().keySet()) {
-                final Map<String,String> contexts = pwmApplication.getConfig().getLdapProfiles().get(ldapProfile).getLoginContexts();
+                final Map<String,String> contexts = pwmApplication.getConfig().getLdapProfiles().get(ldapProfile).getSelectableContexts(pwmApplication);
                 ldapProfiles.put(ldapProfile,contexts);
             }
             settingMap.put("ldapProfiles",ldapProfiles);

+ 1 - 1
src/main/resources/password/pwm/AppProperty.properties

@@ -234,7 +234,7 @@ security.sharedHistory.caseInsensitive=true
 security.sharedHistory.saltLength=64
 security.certs.validateTimestamps=false
 security.ldap.resolveCanonicalDN=true
-security.ldap.canonicalCacheSeconds=30
+security.ldap.canonicalCacheSeconds=600
 security.defaultEphemeralBlockAlg=AES128_GCM
 security.defaultEphemeralHashAlg=SHA512
 security.config.minSecurityKeyLength=32

+ 5 - 3
src/main/webapp/WEB-INF/jsp/fragment/ldap-selector.jsp

@@ -36,6 +36,7 @@
     Map<String,LdapProfile> ldapProfiles = Collections.emptyMap();
     String selectedProfileParam = "";
     LdapProfile selectedProfile = null;
+    Map<String,String> selectableContexts = null;
     boolean showContextSelector = false;
     try {
         final PwmRequest pwmRequest = PwmRequest.forRequest(request, response);
@@ -45,7 +46,8 @@
         selectedProfile = pwmRequest.getConfig().getLdapProfiles().containsKey(selectedProfileParam)
                 ? pwmRequest.getConfig().getLdapProfiles().get(selectedProfileParam)
                 : pwmRequest.getConfig().getDefaultLdapProfile();
-        showContextSelector = selectableContextMode == SelectableContextMode.SHOW_CONTEXTS && selectedProfile != null && selectedProfile.getLoginContexts().size() > 0;
+        selectableContexts = selectedProfile.getSelectableContexts(pwmRequest.getPwmApplication());
+        showContextSelector = selectableContextMode == SelectableContextMode.SHOW_CONTEXTS && selectedProfile != null && selectableContexts.size() > 0;
     } catch (PwmException e) {
         /* noop */
     }
@@ -63,8 +65,8 @@
 <div style="display: <%=showContextSelector?"inherit":"none"%>" id="contextSelectorWrapper">
     <h2><label for="<%=PwmConstants.PARAM_CONTEXT%>"><pwm:display key="Field_Location"/></label></h2>
     <select name="<%=PwmConstants.PARAM_CONTEXT%>" id="<%=PwmConstants.PARAM_CONTEXT%>" class="selectfield">
-        <% for (final String key : selectedProfile.getLoginContexts().keySet()) { %>
-        <option value="<%=StringUtil.escapeHtml(key)%>"><%=StringUtil.escapeHtml(selectedProfile.getLoginContexts().get(key))%></option>
+        <% for (final String key : selectableContexts.keySet()) { %>
+        <option value="<%=StringUtil.escapeHtml(key)%>"><%=StringUtil.escapeHtml(selectableContexts.get(key))%></option>
         <% } %>
     </select>
 </div>

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

@@ -370,7 +370,7 @@ StringArrayValueHandler.valueHandler = function(settingKey, iteration) {
 
     var isLdapDN = PWM_MAIN.JSLibrary.arrayContains(PWM_SETTINGS['settings'][settingKey]['flags'],'ldapDNsyntax');
     if (isLdapDN) {
-        UILibrary.editLdapDN(okAction);
+        UILibrary.editLdapDN(okAction,{currentDN: editorOptions['value']});
     } else {
         UILibrary.stringEditorDialog(editorOptions);
     }
@@ -2426,7 +2426,7 @@ UserPermissionHandler.draw = function(keyName) {
                         PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapProfileID'] = ldapProfileID;
                         PWM_VAR['clientSettingCache'][keyName][rowKey]['ldapBase'] = value;
                         UserPermissionHandler.write(keyName,true);
-                    });
+                    }, {currentDN: currentBaseValue});
                 };
                 if (currentBaseValue && currentBaseValue.length > 0) {
                     UILibrary.addTextValueToElement(inputID + '-base', currentBaseValue);
@@ -2682,7 +2682,7 @@ StringValueHandler.init = function(settingKey) {
                 });
             };
             if (isLdapDN) {
-                UILibrary.editLdapDN(writeBackFunc);
+                UILibrary.editLdapDN(writeBackFunc,{currentDN: value});
             } else {
                 UILibrary.stringEditorDialog({
                     title:'Edit Value - ' + settingData['label'],

+ 50 - 0
src/test/java/password/pwm/config/option/IdentityVerificationMethodEnumTest.java

@@ -0,0 +1,50 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 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.config.option;
+
+import org.junit.Test;
+import password.pwm.PwmConstants;
+import password.pwm.config.Configuration;
+import password.pwm.config.stored.StoredConfigurationImpl;
+import password.pwm.error.PwmUnrecoverableException;
+
+public class IdentityVerificationMethodEnumTest {
+    @Test
+    public void testLabels() throws PwmUnrecoverableException
+    {
+        final Configuration configuration = new Configuration(StoredConfigurationImpl.newStoredConfiguration());
+        for (final IdentityVerificationMethod method : IdentityVerificationMethod.values()) {
+            method.getLabel(configuration, PwmConstants.DEFAULT_LOCALE);
+        }
+    }
+
+    @Test
+    public void testDescriptions() throws PwmUnrecoverableException
+    {
+        final Configuration configuration = new Configuration(StoredConfigurationImpl.newStoredConfiguration());
+        for (final IdentityVerificationMethod category : IdentityVerificationMethod.values()) {
+            category.getDescription(configuration, PwmConstants.DEFAULT_LOCALE);
+        }
+    }
+
+}