/* * 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.ldap; import com.novell.ldapchai.ChaiConstant; import com.novell.ldapchai.ChaiEntry; import com.novell.ldapchai.ChaiFactory; import com.novell.ldapchai.ChaiUser; import com.novell.ldapchai.cr.Answer; import com.novell.ldapchai.exception.ChaiOperationException; import com.novell.ldapchai.exception.ChaiUnavailableException; import com.novell.ldapchai.provider.ChaiConfiguration; import com.novell.ldapchai.provider.ChaiProvider; import com.novell.ldapchai.provider.ChaiProviderFactory; import com.novell.ldapchai.provider.ChaiSetting; import com.novell.ldapchai.util.SearchHelper; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.config.Configuration; import password.pwm.config.FormConfiguration; import password.pwm.config.PwmSetting; import password.pwm.config.profile.LdapProfile; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmSession; 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.svc.stats.Statistic; import password.pwm.svc.stats.StatisticsManager; import password.pwm.util.PasswordData; import password.pwm.util.java.JsonUtil; import password.pwm.util.java.StringUtil; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.util.macro.MacroMachine; import password.pwm.util.secure.X509Utils; import javax.net.ssl.X509TrustManager; import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.TimeUnit; public class LdapOperationsHelper { private static final PwmLogger LOGGER = PwmLogger.forClass(LdapOperationsHelper.class); public static void addConfiguredUserObjectClass( final SessionLabel sessionLabel, final UserIdentity userIdentity, final PwmApplication pwmApplication ) throws ChaiUnavailableException, PwmUnrecoverableException { final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final Set<String> newObjClasses = new HashSet<>(ldapProfile.readSettingAsStringArray(PwmSetting.AUTO_ADD_OBJECT_CLASSES)); if (newObjClasses.isEmpty()) { return; } final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final ChaiUser theUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider); addUserObjectClass(sessionLabel, theUser, newObjClasses); } private static void addUserObjectClass( final SessionLabel sessionLabel, final ChaiUser theUser, final Set<String> newObjClasses ) throws ChaiUnavailableException { String auxClass = null; try { final Set<String> existingObjClasses = theUser.readMultiStringAttribute(ChaiConstant.ATTR_LDAP_OBJECTCLASS); newObjClasses.removeAll(existingObjClasses); for (final String newObjClass : newObjClasses) { auxClass = newObjClass; theUser.addAttribute(ChaiConstant.ATTR_LDAP_OBJECTCLASS, auxClass); LOGGER.info(sessionLabel, "added objectclass '" + auxClass + "' to user " + theUser.getEntryDN()); } } catch (ChaiOperationException e) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append("error adding objectclass '").append(auxClass).append("' to user "); errorMsg.append(theUser.getEntryDN()); errorMsg.append(": "); errorMsg.append(e.toString()); LOGGER.error(sessionLabel,errorMsg.toString()); } } public static ChaiProvider openProxyChaiProvider( final SessionLabel sessionLabel, final LdapProfile ldapProfile, final Configuration config, final StatisticsManager statisticsManager ) throws PwmUnrecoverableException { LOGGER.trace(sessionLabel, "opening new ldap proxy connection"); final String proxyDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); final PasswordData proxyPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD); try { return createChaiProvider(sessionLabel, ldapProfile, config, proxyDN, proxyPW); } catch (ChaiUnavailableException e) { if (statisticsManager != null) { statisticsManager.incrementValue(Statistic.LDAP_UNAVAILABLE_COUNT); } final StringBuilder errorMsg = new StringBuilder(); errorMsg.append("error connecting as proxy user: "); final PwmError pwmError = PwmError.forChaiError(e.getErrorCode()); if (pwmError != null && pwmError != PwmError.ERROR_UNKNOWN) { errorMsg.append(new ErrorInformation(pwmError,e.getMessage()).toDebugStr()); } else { errorMsg.append(e.getMessage()); } final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,errorMsg.toString()); LOGGER.fatal(sessionLabel,"check ldap proxy settings: " + errorInformation.toDebugStr()); throw new PwmUnrecoverableException(errorInformation); } } public static String readLdapGuidValue( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity, final boolean throwExceptionOnError ) throws ChaiUnavailableException, PwmUnrecoverableException { final boolean enableCache = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_CACHE_USER_GUID_ENABLE)); final CacheKey cacheKey = CacheKey.makeCacheKey(LdapOperationsHelper.class, null, "guidValue-" + userIdentity.toDelimitedKey()); if (enableCache) { final String cachedValue = pwmApplication.getCacheService().get(cacheKey); if (cachedValue != null) { return cachedValue; } } final String existingValue = GUIDHelper.readExistingGuidValue( pwmApplication, sessionLabel, userIdentity, throwExceptionOnError ); final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final String guidAttributeName = ldapProfile.readSettingAsString(PwmSetting.LDAP_GUID_ATTRIBUTE); if (existingValue == null || existingValue.length() < 1) { if (!"DN".equalsIgnoreCase(guidAttributeName) && !"VENDORGUID".equalsIgnoreCase(guidAttributeName)) { if (ldapProfile.readSettingAsBoolean(PwmSetting.LDAP_GUID_AUTO_ADD)) { LOGGER.trace("assigning new GUID to user " + userIdentity); return GUIDHelper.assignGuidToUser(pwmApplication, sessionLabel, userIdentity, guidAttributeName); } } final String errorMsg = "unable to resolve GUID value for user " + userIdentity.toString(); GUIDHelper.processError(errorMsg,throwExceptionOnError); } if (enableCache) { final long cacheSeconds = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_CACHE_USER_GUID_SECONDS)); final CachePolicy cachePolicy = CachePolicy.makePolicyWithExpiration(new TimeDuration(cacheSeconds, TimeUnit.SECONDS)); pwmApplication.getCacheService().put(cacheKey, cachePolicy, existingValue); } return existingValue; } /** * Writes a Map of form values to ldap onto the supplied user object. * The map key must be a string of attribute names. * <p/> * Any ldap operation exceptions are not reported (but logged). * * @param pwmSession for looking up session info * @param theUser User to write to * @param formValues A map with {@link password.pwm.config.FormConfiguration} keys and String values. * @throws ChaiUnavailableException if the directory is unavailable * @throws PwmOperationalException if their is an unexpected ldap problem */ public static void writeFormValuesToLdap( final PwmApplication pwmApplication, final PwmSession pwmSession, final ChaiUser theUser, final Map<FormConfiguration,String> formValues, final boolean expandMacros ) throws ChaiUnavailableException, PwmOperationalException, PwmUnrecoverableException { final Map<String,String> tempMap = new HashMap<>(); for (final FormConfiguration formItem : formValues.keySet()) { if (!formItem.isReadonly()) { tempMap.put(formItem.getName(),formValues.get(formItem)); } } final MacroMachine macroMachine = pwmSession.getSessionManager().getMacroMachine(pwmApplication); writeMapToLdap(theUser, tempMap, macroMachine, expandMacros); } /** * Writes a Map of values to ldap onto the supplied user object. * The map key must be a string of attribute names. * <p/> * Any ldap operation exceptions are not reported (but logged). * * @param theUser User to write to * @param valueMap A map with String keys and String values. * @throws ChaiUnavailableException if the directory is unavailable * @throws PwmOperationalException if their is an unexpected ldap problem */ public static void writeMapToLdap( final ChaiUser theUser, final Map<String,String> valueMap, final MacroMachine macroMachine, final boolean expandMacros ) throws PwmOperationalException, ChaiUnavailableException { final Map<String,String> currentValues; try { currentValues = theUser.readStringAttributes(valueMap.keySet()); } catch (ChaiOperationException e) { final String errorMsg = "error reading existing values on user " + theUser.getEntryDN() + " prior to replacing values, error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); final PwmOperationalException newException = new PwmOperationalException(errorInformation); newException.initCause(e); throw newException; } for (final String attrName : valueMap.keySet()) { String attrValue = valueMap.get(attrName) != null ? valueMap.get(attrName) : ""; if (expandMacros) { attrValue = macroMachine.expandMacros(attrValue); } if (!attrValue.equals(currentValues.get(attrName))) { if (attrValue.length() > 0) { try { theUser.writeStringAttribute(attrName, attrValue); LOGGER.info("set attribute on user " + theUser.getEntryDN() + " (" + attrName + "=" + attrValue + ")"); } catch (ChaiOperationException e) { final String errorMsg = "error setting '" + attrName + "' attribute on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); final PwmOperationalException newException = new PwmOperationalException(errorInformation); newException.initCause(e); throw newException; } } else { if (currentValues.get(attrName) != null && currentValues.get(attrName).length() > 0) { try { theUser.deleteAttribute(attrName, null); LOGGER.info("deleted attribute value on user " + theUser.getEntryDN() + " (" + attrName + ")"); } catch (ChaiOperationException e) { final String errorMsg = "error removing '" + attrName + "' attribute value on user " + theUser.getEntryDN() + ", error: " + e.getMessage(); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); final PwmOperationalException newException = new PwmOperationalException(errorInformation); newException.initCause(e); throw newException; } } } } else { LOGGER.debug("skipping attribute modify for attribute '" + attrName + "', no change in value"); } } } private static class GUIDHelper { private static String readExistingGuidValue( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity, final boolean throwExceptionOnError ) throws ChaiUnavailableException, PwmUnrecoverableException { final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity); final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final String guidAttributeName = ldapProfile.readSettingAsString(PwmSetting.LDAP_GUID_ATTRIBUTE); if ("DN".equalsIgnoreCase(guidAttributeName)) { return userIdentity.toDelimitedKey(); } if ("VENDORGUID".equalsIgnoreCase(guidAttributeName)) { try { final String guidValue = theUser.readGUID(); if (guidValue != null && guidValue.length() > 1) { LOGGER.trace(sessionLabel, "read VENDORGUID value for user " + theUser + ": " + guidValue); } else { LOGGER.trace(sessionLabel, "unable to find a VENDORGUID value for user " + theUser.getEntryDN()); } return guidValue; } catch (Exception e) { final String errorMsg = "error while reading vendor GUID value for user " + theUser.getEntryDN() + ", error: " + e.getMessage(); return processError(errorMsg, throwExceptionOnError); } } try { return theUser.readStringAttribute(guidAttributeName); } catch (ChaiOperationException e) { final String errorMsg = "unexpected error while reading attribute GUID value for user " + userIdentity + " from '" + guidAttributeName + "', error: " + e.getMessage(); return processError(errorMsg, throwExceptionOnError); } } private static String processError(final String errorMsg, final boolean throwExceptionOnError) throws PwmUnrecoverableException { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_MISSING_GUID,errorMsg); if (throwExceptionOnError) { throw new PwmUnrecoverableException(errorInformation); } LOGGER.warn(errorMsg); return null; } private static boolean searchForExistingGuidValue( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final String guidValue ) throws ChaiUnavailableException, PwmUnrecoverableException { boolean exists = false; for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) { final String guidAttributeName = ldapProfile.readSettingAsString(PwmSetting.LDAP_GUID_ATTRIBUTE); if (!"DN".equalsIgnoreCase(guidAttributeName) && !"VENDORGUID".equalsIgnoreCase(guidAttributeName)) { try { // check if it is unique final SearchConfiguration searchConfiguration = SearchConfiguration.builder() .filter("(" + guidAttributeName + "=" + guidValue + ")") .build(); final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine(); final UserIdentity result = userSearchEngine.performSingleUserSearch(searchConfiguration, sessionLabel); exists = result != null; } catch (PwmOperationalException e) { if (e.getError() != PwmError.ERROR_CANT_MATCH_USER) { LOGGER.warn(sessionLabel, "error while searching to verify new unique GUID value: " + e.getError()); } } } } return exists; } private static String assignGuidToUser( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity, final String guidAttributeName ) throws ChaiUnavailableException, PwmUnrecoverableException { int attempts = 0; String newGuid = null; while (attempts < 10 && newGuid == null) { attempts++; newGuid = generateGuidValue(pwmApplication, sessionLabel); if (searchForExistingGuidValue(pwmApplication, sessionLabel, newGuid)) { newGuid = null; } } if (newGuid == null) { throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"unable to generate unique GUID value for user " + userIdentity)); } addConfiguredUserObjectClass(sessionLabel, userIdentity, pwmApplication); try { // write it to the directory final ChaiUser chaiUser = pwmApplication.getProxiedChaiUser(userIdentity); chaiUser.writeStringAttribute(guidAttributeName, newGuid); LOGGER.info(sessionLabel, "added GUID value '" + newGuid + "' to user " + userIdentity); return newGuid; } catch (ChaiOperationException e) { final String errorMsg = "unable to write GUID value to user attribute " + guidAttributeName + " : " + e.getMessage() + ", cannot write GUID value to user " + userIdentity; final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg); LOGGER.error(errorInformation.toDebugStr()); throw new PwmUnrecoverableException(errorInformation); } } private static String generateGuidValue( final PwmApplication pwmApplication, final SessionLabel sessionLabel ) throws PwmUnrecoverableException { final MacroMachine macroMachine = MacroMachine.forNonUserSpecific(pwmApplication, sessionLabel); final String guidPattern = pwmApplication.getConfig().readAppProperty(AppProperty.LDAP_GUID_PATTERN); return macroMachine.expandMacros(guidPattern); } } public static ChaiProvider createChaiProvider( final SessionLabel sessionLabel, final LdapProfile ldapProfile, final Configuration config, final String userDN, final PasswordData userPassword ) throws ChaiUnavailableException, PwmUnrecoverableException { final List<String> ldapURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS); final ChaiConfiguration chaiConfig = createChaiConfiguration(config, ldapProfile, ldapURLs, userDN, userPassword); LOGGER.trace(sessionLabel,"creating new ldap connection using config: " + chaiConfig.toString()); return ChaiProviderFactory.createProvider(chaiConfig); } public static ChaiProvider createChaiProvider( final SessionLabel sessionLabel, final Configuration config, final LdapProfile ldapProfile, final List<String> ldapURLs, final String userDN, final PasswordData userPassword ) throws ChaiUnavailableException, PwmUnrecoverableException { final ChaiConfiguration chaiConfig = createChaiConfiguration( config, ldapProfile, ldapURLs, userDN, userPassword); LOGGER.trace(sessionLabel,"creating new ldap connection using config: " + chaiConfig.toString()); return ChaiProviderFactory.createProvider(chaiConfig); } public static ChaiConfiguration createChaiConfiguration( final Configuration config, final LdapProfile ldapProfile ) throws PwmUnrecoverableException { final List<String> ldapURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS); final String userDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); final PasswordData userPassword = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD); return createChaiConfiguration(config, ldapProfile, ldapURLs, userDN, userPassword); } public static ChaiConfiguration createChaiConfiguration( final Configuration config, final LdapProfile ldapProfile, final List<String> ldapURLs, final String userDN, final PasswordData userPassword ) throws PwmUnrecoverableException { final ChaiConfiguration chaiConfig = new ChaiConfiguration(ldapURLs, userDN, userPassword == null ? null : userPassword.getStringValue()); chaiConfig.setSetting(ChaiSetting.PROMISCUOUS_SSL, config.readAppProperty(AppProperty.LDAP_PROMISCUOUS_ENABLE)); { final boolean enableNmasExtensions = Boolean.parseBoolean(config.readAppProperty(AppProperty.LDAP_EXTENSIONS_NMAS_ENABLE)); chaiConfig.setSetting(ChaiSetting.EDIRECTORY_ENABLE_NMAS, Boolean.toString(enableNmasExtensions)); } chaiConfig.setSetting(ChaiSetting.CR_CHAI_STORAGE_ATTRIBUTE, ldapProfile.readSettingAsString(PwmSetting.CHALLENGE_USER_ATTRIBUTE)); chaiConfig.setSetting(ChaiSetting.CR_ALLOW_DUPLICATE_RESPONSES, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_ALLOW_DUPLICATE_RESPONSES))); chaiConfig.setSetting(ChaiSetting.CR_CASE_INSENSITIVE, Boolean.toString(config.readSettingAsBoolean(PwmSetting.CHALLENGE_CASE_INSENSITIVE))); { final String setting = config.readAppProperty(AppProperty.SECURITY_RESPONSES_HASH_ITERATIONS); if (setting != null && setting.length() > 0) { final int intValue = Integer.parseInt(setting); chaiConfig.setSetting(ChaiSetting.CR_CHAI_SALT_COUNT, Integer.toString(intValue)); } } chaiConfig.setSetting(ChaiSetting.JNDI_ENABLE_POOL, "false"); // can cause issues with previous password authentication chaiConfig.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, Answer.FormatType.SHA1_SALT.toString()); final String storageMethodString = config.readSettingAsString(PwmSetting.CHALLENGE_STORAGE_HASHED); try { final Answer.FormatType formatType = Answer.FormatType.valueOf(storageMethodString); chaiConfig.setSetting(ChaiSetting.CR_DEFAULT_FORMAT_TYPE, formatType.toString()); } catch (Exception e) { LOGGER.warn("unknown CR storage format type '" + storageMethodString + "' "); } final X509Certificate[] ldapServerCerts = ldapProfile.readSettingAsCertificate(PwmSetting.LDAP_SERVER_CERTS); if (ldapServerCerts != null && ldapServerCerts.length > 0) { final X509TrustManager tm = new X509Utils.CertMatchingTrustManager(config, ldapServerCerts); chaiConfig.setTrustManager(new X509TrustManager[]{tm}); } final String idleTimeoutMsString = config.readAppProperty(AppProperty.LDAP_CONNECTION_TIMEOUT); chaiConfig.setSetting(ChaiSetting.LDAP_CONNECT_TIMEOUT,idleTimeoutMsString); // set the watchdog idle timeout. final int idleTimeoutMs = (int)config.readSettingAsLong(PwmSetting.LDAP_IDLE_TIMEOUT) * 1000; if (idleTimeoutMs > 0) { chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "true"); chaiConfig.setSetting(ChaiSetting.WATCHDOG_IDLE_TIMEOUT, idleTimeoutMsString); chaiConfig.setSetting(ChaiSetting.WATCHDOG_CHECK_FREQUENCY, Long.toString(5 * 1000)); } else { chaiConfig.setSetting(ChaiSetting.WATCHDOG_ENABLE, "false"); } chaiConfig.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_ENABLE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_ENABLE)); chaiConfig.setSetting(ChaiSetting.LDAP_SEARCH_PAGING_SIZE, config.readAppProperty(AppProperty.LDAP_SEARCH_PAGING_SIZE)); if (config.readSettingAsBoolean(PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET)) { chaiConfig.setSetting(ChaiSetting.AD_SET_POLICY_HINTS_ON_PW_SET,"true"); } // write out any configured values; final String rawValue = config.readAppProperty(AppProperty.LDAP_CHAI_SETTINGS); final String[] rawValues = rawValue != null ? rawValue.split(AppProperty.VALUE_SEPARATOR) : new String[0]; final Map<String, String> configuredSettings = StringUtil.convertStringListToNameValuePair(Arrays.asList(rawValues), "="); for (final String key : configuredSettings.keySet()) { if (key != null && !key.isEmpty()) { final ChaiSetting theSetting = ChaiSetting.forKey(key); if (theSetting == null) { LOGGER.warn("ignoring unknown chai setting '" + key + "'"); } else { chaiConfig.setSetting(theSetting, configuredSettings.get(key)); } } } // set ldap referrals chaiConfig.setSetting(ChaiSetting.LDAP_FOLLOW_REFERRALS,String.valueOf(config.readSettingAsBoolean(PwmSetting.LDAP_FOLLOW_REFERRALS))); // enable wire trace; if (config.readSettingAsBoolean(PwmSetting.LDAP_ENABLE_WIRE_TRACE)) { chaiConfig.setSetting(ChaiSetting.WIRETRACE_ENABLE, "true"); } return chaiConfig; } public static String readLdapUsernameValue( final PwmApplication pwmApplication, final UserIdentity userIdentity ) throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException { final String profileID = userIdentity.getLdapProfileID(); final String uIDattr = pwmApplication.getConfig().getLdapProfiles().get(profileID).getUsernameAttribute(); final UserDataReader userDataReader = LdapUserDataReader.appProxiedReader(pwmApplication, userIdentity); return userDataReader.readStringAttribute(uIDattr); } /** * Update the user's "lastUpdated" attribute. By default this is * "pwmLastUpdate" attribute * * @param userIdentity ldap user to operate on * @return true if successful; * @throws com.novell.ldapchai.exception.ChaiUnavailableException if the * directory is unavailable */ public static boolean updateLastPasswordUpdateAttribute( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity ) throws ChaiUnavailableException, PwmUnrecoverableException { final ChaiUser theUser = pwmApplication.getProxiedChaiUser(userIdentity); boolean success = false; final LdapProfile ldapProfile = pwmApplication.getConfig().getLdapProfiles().get(userIdentity.getLdapProfileID()); final String updateAttribute = ldapProfile.readSettingAsString(PwmSetting.PASSWORD_LAST_UPDATE_ATTRIBUTE); if (updateAttribute != null && updateAttribute.length() > 0) { try { theUser.writeDateAttribute(updateAttribute, new Date()); LOGGER.debug(sessionLabel, "wrote pwdLastModified update attribute for " + theUser.getEntryDN()); success = true; } catch (ChaiOperationException e) { LOGGER.debug(sessionLabel, "error writing update attribute for user '" + theUser.getEntryDN() + "' " + e.getMessage()); } } return success; } public static Map<String, List<String>> readAllEntryAttributeValues(final ChaiEntry chaiEntry) throws ChaiUnavailableException, ChaiOperationException { final SearchHelper searchHelper = new SearchHelper(); searchHelper.setSearchScope(ChaiProvider.SEARCH_SCOPE.BASE); searchHelper.setFilter("(objectClass=*)"); final Map<String, Map<String, List<String>>> results = chaiEntry.getChaiProvider().searchMultiValues(chaiEntry.getEntryDN(), searchHelper); if (!results.isEmpty()) { return results.values().iterator().next(); } return Collections.emptyMap(); } public static Iterator<UserIdentity> readAllUsersFromLdap( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final String searchFilter, final int maxResults ) throws ChaiUnavailableException, ChaiOperationException, PwmUnrecoverableException, PwmOperationalException { final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine(); final SearchConfiguration searchConfiguration; { final SearchConfiguration.SearchConfigurationBuilder builder = SearchConfiguration.builder(); builder.enableValueEscaping(false); builder.searchTimeout(Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.REPORTING_LDAP_SEARCH_TIMEOUT))); if (searchFilter == null) { builder.username("*"); } else { builder.filter(searchFilter); } searchConfiguration = builder.build(); } LOGGER.debug(sessionLabel,"beginning user search using parameters: " + (JsonUtil.serialize(searchConfiguration))); final Map<UserIdentity,Map<String,String>> searchResults = userSearchEngine.performMultiUserSearch( searchConfiguration, maxResults, Collections.emptyList(), sessionLabel ); LOGGER.debug(sessionLabel,"user search found " + searchResults.size() + " users"); final Queue<UserIdentity> tempQueue = new LinkedList<>(searchResults.keySet()); return new Iterator<UserIdentity>() { @Override public boolean hasNext() { return tempQueue.peek() != null; } @Override public UserIdentity next() { return tempQueue.poll(); } }; } public static Instant readPasswordExpirationTime(final ChaiUser theUser) { try { Date ldapPasswordExpirationTime = theUser.readPasswordExpirationDate(); if (ldapPasswordExpirationTime != null && ldapPasswordExpirationTime.getTime() < 0) { // If ldapPasswordExpirationTime is less than 0, this may indicate an extremely late date, past the epoch. ldapPasswordExpirationTime = null; } return ldapPasswordExpirationTime == null ? null : ldapPasswordExpirationTime.toInstant(); } catch (Exception e) { LOGGER.warn("error reading password expiration time: " + e.getMessage()); } return null; } public static PasswordData readLdapPassword( final PwmApplication pwmApplication, final SessionLabel sessionLabel, final UserIdentity userIdentity ) throws ChaiUnavailableException, PwmUnrecoverableException { if (userIdentity == null || userIdentity.getUserDN() == null || userIdentity.getUserDN().length() < 1) { throw new NullPointerException("invalid user (null)"); } final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()); final ChaiUser chaiUser = ChaiFactory.createChaiUser(userIdentity.getUserDN(), chaiProvider); // use chai (nmas) to retrieve user password if (pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_USER_PWD)) { String currentPass = null; try { final String readPassword = chaiUser.readPassword(); if (readPassword != null && readPassword.length() > 0) { currentPass = readPassword; LOGGER.debug(sessionLabel,"successfully retrieved user's current password from ldap, now conducting standard authentication"); } } catch (Exception e) { LOGGER.debug(sessionLabel, "unable to retrieve user password from ldap: " + e.getMessage()); } // actually do the authentication since we have user pw. if (currentPass != null && currentPass.length() > 0) { return new PasswordData(currentPass); } } else { LOGGER.trace(sessionLabel, "skipping attempt to read user password, option disabled"); } return null; } }