/* * 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.health; import com.novell.ldapchai.ChaiEntry; import com.novell.ldapchai.ChaiFactory; import com.novell.ldapchai.ChaiUser; import com.novell.ldapchai.exception.ChaiError; import com.novell.ldapchai.exception.ChaiErrors; import com.novell.ldapchai.exception.ChaiException; 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.ChaiUtility; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.PwmConstants; import password.pwm.bean.PasswordStatus; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.bean.UserInfoBean; import password.pwm.config.Configuration; import password.pwm.config.PwmSetting; import password.pwm.config.PwmSettingCategory; import password.pwm.config.PwmSettingFlag; import password.pwm.config.PwmSettingSyntax; import password.pwm.config.UserPermission; import password.pwm.config.profile.LdapProfile; import password.pwm.config.profile.PwmPasswordPolicy; import password.pwm.config.profile.PwmPasswordRule; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.ldap.LdapOperationsHelper; import password.pwm.ldap.UserStatusReader; import password.pwm.util.PasswordData; import password.pwm.util.RandomPasswordGenerator; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.TimeDuration; import password.pwm.util.logging.PwmLogger; import password.pwm.util.operations.PasswordUtility; import password.pwm.ws.server.rest.bean.HealthData; import java.io.Serializable; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URI; import java.net.UnknownHostException; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; public class LDAPStatusChecker implements HealthChecker { private static final PwmLogger LOGGER = PwmLogger.forClass(LDAPStatusChecker.class); private static final String TOPIC = "LDAP"; public List<HealthRecord> doHealthCheck(final PwmApplication pwmApplication) { final Configuration config = pwmApplication.getConfig(); final List<HealthRecord> returnRecords = new ArrayList<>(); final Map<String,LdapProfile> ldapProfiles = pwmApplication.getConfig().getLdapProfiles(); for (final String profileID : ldapProfiles.keySet()) { final List<HealthRecord> profileRecords = new ArrayList<>(); profileRecords.addAll( checkBasicLdapConnectivity(pwmApplication, config, ldapProfiles.get(profileID), true)); if (profileRecords.isEmpty()) { profileRecords.addAll(checkLdapServerUrls(config, ldapProfiles.get(profileID))); } if (profileRecords.isEmpty()) { profileRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_OK)); profileRecords.addAll(doLdapTestUserCheck(config, ldapProfiles.get(profileID), pwmApplication)); } returnRecords.addAll(profileRecords); } for (final LdapProfile ldapProfile : pwmApplication.getLdapConnectionService().getLastLdapFailure().keySet()) { final ErrorInformation errorInfo = pwmApplication.getLdapConnectionService().getLastLdapFailure().get(ldapProfile); if (errorInfo != null) { final TimeDuration errorAge = TimeDuration.fromCurrent(errorInfo.getDate()); final long cautionDurationMS = Long.parseLong(pwmApplication.getConfig().readAppProperty(AppProperty.HEALTH_LDAP_CAUTION_DURATION_MS)); if (errorAge.isShorterThan(cautionDurationMS)) { final String ageString = errorAge.asLongString(); final String errorDate = JavaHelper.toIsoDate(errorInfo.getDate()); final String errorMsg = errorInfo.toDebugStr(); returnRecords.add(HealthRecord.forMessage( HealthMessage.LDAP_RecentlyUnreachable, ldapProfile.getDisplayName(PwmConstants.DEFAULT_LOCALE), ageString, errorDate, errorMsg )); } } } returnRecords.addAll(checkVendorSameness(pwmApplication)); returnRecords.addAll(checkUserPermissionValues(pwmApplication)); returnRecords.addAll(checkLdapDNSyntaxValues(pwmApplication)); return returnRecords; } public List<HealthRecord> doLdapTestUserCheck(final Configuration config, final LdapProfile ldapProfile, final PwmApplication pwmApplication) { final String testUserDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_TEST_USER_DN); final String proxyUserDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); final PasswordData proxyUserPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD); final List<HealthRecord> returnRecords = new ArrayList<>(); if (testUserDN == null || testUserDN.length() < 1) { return returnRecords; } if (proxyUserDN.equalsIgnoreCase(testUserDN)) { returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_ProxyTestSameUser, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), PwmSetting.LDAP_PROXY_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE) )); return returnRecords; } ChaiUser theUser = null; ChaiProvider chaiProvider = null; try { try { chaiProvider = LdapOperationsHelper.createChaiProvider( SessionLabel.HEALTH_SESSION_LABEL, ldapProfile, config, proxyUserDN, proxyUserPW ); theUser = ChaiFactory.createChaiUser(testUserDN, chaiProvider); } catch (ChaiUnavailableException e) { returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserUnavailable, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), e.getMessage() )); return returnRecords; } catch (Throwable e) { final String msgString = e.getMessage(); LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL, "unexpected error while testing test user (during object creation): message=" + msgString + " debug info: " + JavaHelper.readHostileExceptionMessage(e)); returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserUnexpected, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), msgString )); return returnRecords; } try { theUser.readObjectClass(); } catch (ChaiException e) { returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserError, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), e.getMessage() )); return returnRecords; } LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL, "beginning process to check ldap test user password read/write operations for profile " + ldapProfile.getIdentifier()); try { final boolean readPwdEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.EDIRECTORY_READ_USER_PWD) && theUser.getChaiProvider().getDirectoryVendor() == ChaiProvider.DIRECTORY_VENDOR.NOVELL_EDIRECTORY; if (readPwdEnabled) { try { theUser.readPassword(); } catch (Exception e) { LOGGER.debug(SessionLabel.HEALTH_SESSION_LABEL, "error reading user password from directory " + e.getMessage()); returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserReadPwError, PwmSetting.EDIRECTORY_READ_USER_PWD.toMenuLocationDebug(null, PwmConstants.DEFAULT_LOCALE), PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), e.getMessage() )); return returnRecords; } } else { final Locale locale = PwmConstants.DEFAULT_LOCALE; final UserIdentity userIdentity = new UserIdentity(testUserDN, ldapProfile.getIdentifier()); final PwmPasswordPolicy passwordPolicy = PasswordUtility.readPasswordPolicyForUser( pwmApplication, null, userIdentity, theUser, locale); boolean doPasswordChange = true; final int minLifetimeSeconds = passwordPolicy.getRuleHelper().readIntValue(PwmPasswordRule.MinimumLifetime); if (minLifetimeSeconds > 0) { final Instant pwdLastModified = PasswordUtility.determinePwdLastModified( pwmApplication, SessionLabel.HEALTH_SESSION_LABEL, userIdentity ); final PasswordStatus passwordStatus; { final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, SessionLabel.HEALTH_SESSION_LABEL); passwordStatus = userStatusReader.readPasswordStatus(theUser, passwordPolicy, null, null); } try { PasswordUtility.checkIfPasswordWithinMinimumLifetime( theUser, SessionLabel.HEALTH_SESSION_LABEL, passwordPolicy, pwdLastModified, passwordStatus ); } catch (PwmException e) { LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL, "skipping test user password set: " + e.getMessage()); doPasswordChange = false; } } if (doPasswordChange) { final PasswordData newPassword = RandomPasswordGenerator.createRandomPassword(null, passwordPolicy, pwmApplication); try { theUser.setPassword(newPassword.getStringValue()); LOGGER.debug(SessionLabel.HEALTH_SESSION_LABEL, "set random password on test user " + userIdentity.toDisplayString()); } catch (ChaiException e) { returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserWritePwError, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), e.getMessage() )); return returnRecords; } } } } catch (Exception e) { final String msg = "error setting test user password: " + JavaHelper.readHostileExceptionMessage(e); LOGGER.error(SessionLabel.HEALTH_SESSION_LABEL, msg, e); returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserUnexpected, PwmSetting.LDAP_TEST_USER_DN.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), msg )); return returnRecords; } try { final UserIdentity userIdentity = new UserIdentity(theUser.getEntryDN(),ldapProfile.getIdentifier()); final UserStatusReader.Settings readerSettings = new UserStatusReader.Settings(); final UserStatusReader userStatusReader = new UserStatusReader( pwmApplication, SessionLabel.HEALTH_SESSION_LABEL, readerSettings ); userStatusReader.populateUserInfoBean( new UserInfoBean(), PwmConstants.DEFAULT_LOCALE, userIdentity, chaiProvider ); } catch (PwmUnrecoverableException e) { returnRecords.add(new HealthRecord( HealthStatus.WARN, makeLdapTopic(ldapProfile, config), "unable to read test user data: " + e.getMessage())); return returnRecords; } } finally { if (chaiProvider != null) { try { chaiProvider.close(); } catch (Exception e) { // ignore } } } returnRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_TestUserOK, ldapProfile.getDisplayName(PwmConstants.DEFAULT_LOCALE))); return returnRecords; } public List<HealthRecord> checkLdapServerUrls(final Configuration config, final LdapProfile ldapProfile) { final List<HealthRecord> returnRecords = new ArrayList<>(); final List<String> serverURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS); for (final String loopURL : serverURLs) { final String proxyDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); ChaiProvider chaiProvider = null; try { chaiProvider = LdapOperationsHelper.createChaiProvider( SessionLabel.HEALTH_SESSION_LABEL, config, ldapProfile, Collections.singletonList(loopURL), proxyDN, ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD) ); final ChaiUser proxyUser = ChaiFactory.createChaiUser(proxyDN, chaiProvider); proxyUser.isValid(); } catch (Exception e) { final String errorString = "error connecting to ldap server '" + loopURL + "': " + e.getMessage(); returnRecords.add(new HealthRecord( HealthStatus.WARN, makeLdapTopic(ldapProfile, config), errorString)); } finally { if (chaiProvider != null) { try { chaiProvider.close(); } catch (Exception e) { /* ignore */ } } } } return returnRecords; } public List<HealthRecord> checkBasicLdapConnectivity( final PwmApplication pwmApplication, final Configuration config, final LdapProfile ldapProfile, final boolean testContextlessRoot ) { final List<HealthRecord> returnRecords = new ArrayList<>(); ChaiProvider chaiProvider = null; try{ ChaiProvider.DIRECTORY_VENDOR directoryVendor = null; try { final String proxyDN = ldapProfile.readSettingAsString(PwmSetting.LDAP_PROXY_USER_DN); final PasswordData proxyPW = ldapProfile.readSettingAsPassword(PwmSetting.LDAP_PROXY_USER_PASSWORD); if (proxyDN == null || proxyDN.length() < 1) { return Collections.singletonList(new HealthRecord(HealthStatus.WARN,HealthTopic.LDAP,"Missing Proxy User DN")); } if (proxyPW == null) { return Collections.singletonList(new HealthRecord(HealthStatus.WARN,HealthTopic.LDAP,"Missing Proxy User Password")); } chaiProvider = LdapOperationsHelper.createChaiProvider(SessionLabel.HEALTH_SESSION_LABEL,ldapProfile,config,proxyDN,proxyPW); final ChaiEntry adminEntry = ChaiFactory.createChaiEntry(proxyDN,chaiProvider); adminEntry.isValid(); directoryVendor = chaiProvider.getDirectoryVendor(); } catch (ChaiException e) { final ChaiError chaiError = ChaiErrors.getErrorForMessage(e.getMessage()); final PwmError pwmError = PwmError.forChaiError(chaiError); final StringBuilder errorString = new StringBuilder(); final String profileName = ldapProfile.getIdentifier(); errorString.append("error connecting to ldap directory (").append(profileName).append("), error: ").append(e.getMessage()); if (chaiError != null && chaiError != ChaiError.UNKNOWN) { errorString.append(" ("); errorString.append(chaiError.toString()); if (pwmError != null && pwmError != PwmError.ERROR_UNKNOWN) { errorString.append(" - "); errorString.append(pwmError.getLocalizedMessage(PwmConstants.DEFAULT_LOCALE, pwmApplication.getConfig())); } errorString.append(")"); } returnRecords.add(new HealthRecord( HealthStatus.WARN, makeLdapTopic(ldapProfile, config), errorString.toString())); pwmApplication.getLdapConnectionService().setLastLdapFailure(ldapProfile, new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,errorString.toString())); return returnRecords; } catch (Exception e) { final HealthRecord record = HealthRecord.forMessage(HealthMessage.LDAP_No_Connection, e.getMessage()); returnRecords.add(record); pwmApplication.getLdapConnectionService().setLastLdapFailure(ldapProfile, new ErrorInformation(PwmError.ERROR_DIRECTORY_UNAVAILABLE,record.getDetail(PwmConstants.DEFAULT_LOCALE,pwmApplication.getConfig()))); return returnRecords; } if (directoryVendor != null && directoryVendor == ChaiProvider.DIRECTORY_VENDOR.MICROSOFT_ACTIVE_DIRECTORY) { returnRecords.addAll(checkAd(pwmApplication, config, ldapProfile)); } if (testContextlessRoot) { for (final String loopContext : ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_CONTEXTLESS_ROOT)) { try { final ChaiEntry contextEntry = ChaiFactory.createChaiEntry(loopContext,chaiProvider); final Set<String> objectClasses = contextEntry.readObjectClass(); if (objectClasses == null || objectClasses.isEmpty()) { final String errorString = "ldap context setting '" + loopContext + "' is not valid"; returnRecords.add(new HealthRecord(HealthStatus.WARN, makeLdapTopic(ldapProfile, config), errorString)); } } catch (Exception e) { final String errorString = "ldap root context '" + loopContext + "' is not valid: " + e.getMessage(); returnRecords.add(new HealthRecord(HealthStatus.WARN, makeLdapTopic(ldapProfile, config), errorString)); } } } } finally { if (chaiProvider != null) { try { chaiProvider.close(); } catch (Exception e) { /* ignore */ } } } return returnRecords; } private static List<HealthRecord> checkAd(final PwmApplication pwmApplication, final Configuration config, final LdapProfile ldapProfile) { final List<HealthRecord> returnList = new ArrayList<>(); final List<String> serverURLs = ldapProfile.readSettingAsStringArray(PwmSetting.LDAP_SERVER_URLS); for (final String loopURL : serverURLs) { try { if (!urlUsingHostname(loopURL)) { returnList.add(HealthRecord.forMessage( HealthMessage.LDAP_AD_StaticIP, loopURL )); } final URI uri= URI.create(loopURL); final String scheme = uri.getScheme(); if ("ldap".equalsIgnoreCase(scheme)) { returnList.add(HealthRecord.forMessage( HealthMessage.LDAP_AD_Unsecure, loopURL )); } } catch (MalformedURLException | UnknownHostException e) { returnList.add(HealthRecord.forMessage( HealthMessage.Config_ParseError, e.getMessage(), PwmSetting.LDAP_SERVER_URLS.toMenuLocationDebug(ldapProfile.getIdentifier(), PwmConstants.DEFAULT_LOCALE), loopURL )); } } returnList.addAll(checkAdPasswordPolicyApi(pwmApplication)); return returnList; } private static boolean urlUsingHostname(final String inputURL) throws MalformedURLException, UnknownHostException { final URI uri = URI.create(inputURL); final String host = uri.getHost(); final InetAddress inetAddress = InetAddress.getByName(host); if (inetAddress != null && inetAddress.getHostName() != null && inetAddress.getHostName().equalsIgnoreCase(host)) { return true; } return false; } private static String makeLdapTopic( final LdapProfile ldapProfile, final Configuration configuration ) { return makeLdapTopic(ldapProfile.getIdentifier(), configuration); } private static String makeLdapTopic( final String profileID, final Configuration configuration ) { if (configuration.getLdapProfiles().isEmpty() || configuration.getLdapProfiles().size() < 2) { return TOPIC; } return TOPIC + "-" + profileID; } private List<HealthRecord> checkVendorSameness(final PwmApplication pwmApplication) { final Map<HealthMonitor.HealthMonitorFlag,Serializable> healthProperties = pwmApplication.getHealthMonitor().getHealthProperties(); if (healthProperties.containsKey(HealthMonitor.HealthMonitorFlag.LdapVendorSameCheck)) { return (List<HealthRecord>)healthProperties.get(HealthMonitor.HealthMonitorFlag.LdapVendorSameCheck); } LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL,"beginning check for replica vendor sameness"); boolean errorReachingServer = false; final Map<String,ChaiProvider.DIRECTORY_VENDOR> replicaVendorMap = new HashMap<>(); try { for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) { final ChaiConfiguration profileChaiConfiguration = LdapOperationsHelper.createChaiConfiguration( pwmApplication.getConfig(), ldapProfile ); final Collection<ChaiConfiguration> replicaConfigs = ChaiUtility.splitConfigurationPerReplica(profileChaiConfiguration, Collections.<ChaiSetting,String>emptyMap()); for (final ChaiConfiguration chaiConfiguration : replicaConfigs) { final ChaiProvider loopProvider = ChaiProviderFactory.createProvider(chaiConfiguration); replicaVendorMap.put(chaiConfiguration.getSetting(ChaiSetting.BIND_URLS),loopProvider.getDirectoryVendor()); } } } catch (Exception e) { errorReachingServer = true; LOGGER.error(SessionLabel.HEALTH_SESSION_LABEL,"error during replica vendor sameness check: " + e.getMessage()); } final ArrayList<HealthRecord> healthRecords = new ArrayList<>(); final Set<ChaiProvider.DIRECTORY_VENDOR> discoveredVendors = new HashSet<>(replicaVendorMap.values()); if (discoveredVendors.size() >= 2) { final StringBuilder vendorMsg = new StringBuilder(); for (final Iterator<String> iterator = replicaVendorMap.keySet().iterator(); iterator.hasNext(); ) { final String key = iterator.next(); vendorMsg.append(key).append("=").append(replicaVendorMap.get(key).toString()); if (iterator.hasNext()) { vendorMsg.append(", "); } } healthRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_VendorsNotSame, vendorMsg.toString())); // cache the error healthProperties.put(HealthMonitor.HealthMonitorFlag.LdapVendorSameCheck, healthRecords); LOGGER.warn(SessionLabel.HEALTH_SESSION_LABEL,"multiple ldap vendors found: " + vendorMsg.toString()); } else if (discoveredVendors.size() == 1) { if (!errorReachingServer) { // cache the no errors healthProperties.put(HealthMonitor.HealthMonitorFlag.LdapVendorSameCheck, healthRecords); } } return healthRecords; } private static List<HealthRecord> checkAdPasswordPolicyApi(final PwmApplication pwmApplication) { final boolean passwordPolicyApiEnabled = pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET); if (!passwordPolicyApiEnabled) { return Collections.emptyList(); } final Map<HealthMonitor.HealthMonitorFlag,Serializable> healthProperties = pwmApplication.getHealthMonitor().getHealthProperties(); if (healthProperties.containsKey(HealthMonitor.HealthMonitorFlag.AdPasswordPolicyApiCheck)) { return (List<HealthRecord>)healthProperties.get(HealthMonitor.HealthMonitorFlag.AdPasswordPolicyApiCheck); } LOGGER.trace(SessionLabel.HEALTH_SESSION_LABEL,"beginning check for ad api password policy (asn " + PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN + ") support"); boolean errorReachingServer = false; final ArrayList<HealthRecord> healthRecords = new ArrayList<>(); try { for (final LdapProfile ldapProfile : pwmApplication.getConfig().getLdapProfiles().values()) { final ChaiConfiguration profileChaiConfiguration = LdapOperationsHelper.createChaiConfiguration( pwmApplication.getConfig(), ldapProfile ); final Collection<ChaiConfiguration> replicaConfigs = ChaiUtility.splitConfigurationPerReplica(profileChaiConfiguration, Collections.<ChaiSetting,String>emptyMap()); for (final ChaiConfiguration chaiConfiguration : replicaConfigs) { final ChaiProvider loopProvider = ChaiProviderFactory.createProvider(chaiConfiguration); final ChaiEntry rootDSE = ChaiUtility.getRootDSE(loopProvider); final Set<String> controls = rootDSE.readMultiStringAttribute("supportedControl"); final boolean asnSupported = controls.contains(PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN); if (!asnSupported) { final String url = chaiConfiguration.getSetting(ChaiSetting.BIND_URLS); final HealthRecord record = HealthRecord.forMessage( HealthMessage.LDAP_Ad_History_Asn_Missing, PwmSetting.AD_ENFORCE_PW_HISTORY_ON_SET.toMenuLocationDebug( null, PwmConstants.DEFAULT_LOCALE), url ); healthRecords.add(record); LOGGER.warn(record.toDebugString(PwmConstants.DEFAULT_LOCALE,pwmApplication.getConfig())); } } } } catch (Exception e) { errorReachingServer = true; LOGGER.error(SessionLabel.HEALTH_SESSION_LABEL, "error during ad api password policy (asn " + PwmConstants.LDAP_AD_PASSWORD_POLICY_CONTROL_ASN + ") check: " + e.getMessage()); } if (!errorReachingServer) { healthProperties.put(HealthMonitor.HealthMonitorFlag.AdPasswordPolicyApiCheck, healthRecords); } return healthRecords; } private static List<HealthRecord> checkUserPermissionValues(final PwmApplication pwmApplication) { final List<HealthRecord> returnList = new ArrayList<>(); final Configuration config= pwmApplication.getConfig(); for (final PwmSetting pwmSetting : PwmSetting.values()) { if (!pwmSetting.isHidden() && pwmSetting.getSyntax() == PwmSettingSyntax.USER_PERMISSION) { if (!pwmSetting.getCategory().hasProfiles()) { final List<UserPermission> userPermissions = config.readSettingAsUserPermission(pwmSetting); for (final UserPermission userPermission : userPermissions) { try { returnList.addAll(checkUserPermission(pwmApplication, userPermission, pwmSetting)); } catch (PwmUnrecoverableException e) { LOGGER.error("error checking configured permission settings:" + e.getMessage()); } } } } } return returnList; } private static List<HealthRecord> checkLdapDNSyntaxValues(final PwmApplication pwmApplication) { final List<HealthRecord> returnList = new ArrayList<>(); final Configuration config = pwmApplication.getConfig(); try { for (final PwmSetting pwmSetting : PwmSetting.values()) { if (!pwmSetting.isHidden() && pwmSetting.getCategory() == PwmSettingCategory.LDAP_PROFILE && pwmSetting.getFlags().contains(PwmSettingFlag.ldapDNsyntax)) { for (final String profile : config.getLdapProfiles().keySet()) { if (pwmSetting.getSyntax() == PwmSettingSyntax.STRING) { final String value = config.getLdapProfiles().get(profile).readSettingAsString(pwmSetting); if (value != null && !value.isEmpty()) { final String errorMsg = validateDN(pwmApplication, value, profile); if (errorMsg != null) { returnList.add(HealthRecord.forMessage(HealthMessage.Config_DNValueValidity, pwmSetting.toMenuLocationDebug(profile, PwmConstants.DEFAULT_LOCALE), errorMsg)); } } } else if (pwmSetting.getSyntax() == PwmSettingSyntax.STRING_ARRAY) { final List<String> values = config.getLdapProfiles().get(profile).readSettingAsStringArray(pwmSetting); if (values != null) { for (final String value : values) { final String errorMsg = validateDN(pwmApplication, value, profile); if (errorMsg != null) { returnList.add(HealthRecord.forMessage(HealthMessage.Config_DNValueValidity, pwmSetting.toMenuLocationDebug(profile, PwmConstants.DEFAULT_LOCALE), errorMsg)); } } } } } } } } catch (PwmUnrecoverableException e) { LOGGER.warn("error while checking DN ldap syntax values: " + e.getMessage()); } return returnList; } private static List<HealthRecord> checkUserPermission( final PwmApplication pwmApplication, final UserPermission userPermission, final PwmSetting pwmSetting ) throws PwmUnrecoverableException { final String settingDebugName = pwmSetting.toMenuLocationDebug(null,PwmConstants.DEFAULT_LOCALE); final List<HealthRecord> returnList = new ArrayList<>(); final Configuration config = pwmApplication.getConfig(); final List<String> ldapProfilesToCheck = new ArrayList<>(); { final String configuredLdapProfileID = userPermission.getLdapProfileID(); if (configuredLdapProfileID == null || configuredLdapProfileID.isEmpty() || configuredLdapProfileID.equals(PwmConstants.PROFILE_ID_ALL)) { ldapProfilesToCheck.addAll(config.getLdapProfiles().keySet()); } else { if (config.getLdapProfiles().keySet().contains(configuredLdapProfileID)) { ldapProfilesToCheck.add(configuredLdapProfileID); } else { return Collections.singletonList( HealthRecord.forMessage(HealthMessage.Config_UserPermissionValidity, settingDebugName, "specified ldap profile ID invalid: " + configuredLdapProfileID )); } } } for (final String ldapProfileID : ldapProfilesToCheck) { switch (userPermission.getType()) { case ldapGroup: { final String groupDN = userPermission.getLdapBase(); if (groupDN != null && !isExampleDN(groupDN)) { final String errorMsg = validateDN(pwmApplication, groupDN, ldapProfileID); if (errorMsg != null) { returnList.add(HealthRecord.forMessage(HealthMessage.Config_UserPermissionValidity, settingDebugName, "groupDN: " + errorMsg)); } } } break; case ldapQuery: { final String baseDN = userPermission.getLdapBase(); if (baseDN != null && !isExampleDN(baseDN)) { final String errorMsg = validateDN(pwmApplication, baseDN, ldapProfileID); if (errorMsg != null) { returnList.add(HealthRecord.forMessage(HealthMessage.Config_UserPermissionValidity, settingDebugName, "baseDN: " + errorMsg)); } } } break; default: JavaHelper.unhandledSwitchStatement(userPermission.getType()); } } return returnList; } private static String validateDN(final PwmApplication pwmApplication, final String dnValue, final String ldapProfileID) throws PwmUnrecoverableException { final ChaiProvider chaiProvider = pwmApplication.getProxyChaiProvider(ldapProfileID); try { if (!isExampleDN(dnValue)) { final ChaiEntry baseDNEntry = ChaiFactory.createChaiEntry(dnValue, chaiProvider); if (!baseDNEntry.isValid()) { return "DN '" + dnValue + "' is invalid"; } else { final String canonicalDN = baseDNEntry.readCanonicalDN(); if (!dnValue.equals(canonicalDN)) { return "DN '" + dnValue + "' is not the correct canonical value, the server reports the canonical value as '" + canonicalDN + "'"; } } } } catch (ChaiUnavailableException e) { throw PwmUnrecoverableException.fromChaiException(e); } catch (ChaiException e) { LOGGER.error("error while evaluating ldap DN '" + dnValue + "', error: " + e.getMessage()); } return null; } private static boolean isExampleDN(final String dnValue) { if (dnValue == null) { return false; } final String[] EXAMPLE_SUFFIXES = new String[]{ "DC=site,DC=example,DC=net", "ou=groups,o=example", }; for (final String suffix : EXAMPLE_SUFFIXES) { if (dnValue.endsWith(suffix)) { return true; } } return false; } public static HealthData healthForNewConfiguration( final PwmApplication pwmApplication, final Configuration config, final Locale locale, final String profileID, final boolean testContextless, final boolean fullTest ) throws PwmUnrecoverableException { final PwmApplication tempApplication = new PwmApplication(pwmApplication.getPwmEnvironment().makeRuntimeInstance(config)); final LDAPStatusChecker ldapStatusChecker = new LDAPStatusChecker(); final List<HealthRecord> profileRecords = new ArrayList<>(); final LdapProfile ldapProfile = config.getLdapProfiles().get(profileID); profileRecords.addAll(ldapStatusChecker.checkBasicLdapConnectivity(tempApplication, config, ldapProfile, testContextless)); if (fullTest) { profileRecords.addAll(ldapStatusChecker.checkLdapServerUrls(config, ldapProfile)); } if (profileRecords.isEmpty()) { profileRecords.add(HealthRecord.forMessage(HealthMessage.LDAP_OK)); } if (fullTest) { profileRecords.addAll(ldapStatusChecker.doLdapTestUserCheck(config, ldapProfile, tempApplication)); } return HealthRecord.asHealthDataBean(config, locale, profileRecords); } }