/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library 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 Lesser General Public License for more * details. */ package com.liferay.portal.security.ldap.internal.authenticator; import com.liferay.admin.kernel.util.Omniadmin; import com.liferay.portal.kernel.exception.PasswordExpiredException; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.UserLockoutException; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.User; import com.liferay.portal.kernel.security.auth.AuthException; import com.liferay.portal.kernel.security.auth.Authenticator; import com.liferay.portal.kernel.security.ldap.LDAPSettings; import com.liferay.portal.kernel.security.pwd.PasswordEncryptor; import com.liferay.portal.kernel.service.UserLocalService; import com.liferay.portal.kernel.util.AutoResetThreadLocal; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.MapUtil; import com.liferay.portal.kernel.util.Props; import com.liferay.portal.kernel.util.PropsKeys; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.security.ldap.PortalLDAP; import com.liferay.portal.security.ldap.authenticator.configuration.LDAPAuthConfiguration; import com.liferay.portal.security.ldap.configuration.ConfigurationProvider; import com.liferay.portal.security.ldap.configuration.LDAPServerConfiguration; import com.liferay.portal.security.ldap.configuration.SystemLDAPConfiguration; import com.liferay.portal.security.ldap.constants.LDAPConstants; import com.liferay.portal.security.ldap.exportimport.LDAPUserImporter; import com.liferay.portal.security.ldap.exportimport.configuration.LDAPImportConfiguration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Properties; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferencePolicyOption; /** * @author Brian Wing Shun Chan * @author Scott Lee * @author Josef Sustacek */ @Component( immediate = true, property = {"key=auth.pipeline.pre"}, service = Authenticator.class ) public class LDAPAuth implements Authenticator { public static final String RESULT_PASSWORD_EXP_WARNING = "2.16.840.1.113730.3.4.5"; public static final String RESULT_PASSWORD_RESET = "2.16.840.1.113730.3.4.4"; @Override public int authenticateByEmailAddress( long companyId, String emailAddress, String password, Map<String, String[]> headerMap, Map<String, String[]> parameterMap) throws AuthException { try { return authenticate( companyId, emailAddress, StringPool.BLANK, 0, password); } catch (Exception e) { _log.error(e, e); throw new AuthException(e); } } @Override public int authenticateByScreenName( long companyId, String screenName, String password, Map<String, String[]> headerMap, Map<String, String[]> parameterMap) throws AuthException { try { return authenticate( companyId, StringPool.BLANK, screenName, 0, password); } catch (Exception e) { _log.error(e, e); throw new AuthException(e); } } @Override public int authenticateByUserId( long companyId, long userId, String password, Map<String, String[]> headerMap, Map<String, String[]> parameterMap) throws AuthException { try { return authenticate( companyId, StringPool.BLANK, StringPool.BLANK, userId, password); } catch (Exception e) { _log.error(e, e); throw new AuthException(e); } } @Activate protected void activate(Map<String, Object> properties) { _authPipelineEnableLiferayCheck = GetterUtil.getBoolean( _props.get(PropsKeys.AUTH_PIPELINE_ENABLE_LIFERAY_CHECK)); } protected LDAPAuthResult authenticate( LdapContext ctx, long companyId, Attributes attributes, String userDN, String password) throws Exception { LDAPAuthResult ldapAuthResult = null; // Check passwords by either doing a comparison between the passwords or // by binding to the LDAP server. If using LDAP password policies, bind // auth method must be used in order to get the result control codes. LDAPAuthConfiguration ldapAuthConfiguration = _ldapAuthConfigurationProvider.getConfiguration(companyId); String authMethod = ldapAuthConfiguration.method(); SystemLDAPConfiguration systemLDAPConfiguration = _systemLDAPConfigurationProvider.getConfiguration(companyId); if (authMethod.equals(LDAPConstants.AUTH_METHOD_BIND)) { Hashtable<String, Object> env = (Hashtable<String, Object>)ctx.getEnvironment(); env.put(Context.REFERRAL, systemLDAPConfiguration.referral()); env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_PRINCIPAL, userDN); // Do not use pooling because principal changes env.put("com.sun.jndi.ldap.connect.pool", "false"); ldapAuthResult = getFailedLDAPAuthResult(env); if (ldapAuthResult != null) { return ldapAuthResult; } ldapAuthResult = new LDAPAuthResult(); InitialLdapContext initialLdapContext = null; try { initialLdapContext = new InitialLdapContext(env, null); // Get LDAP bind results Control[] responseControls = initialLdapContext.getResponseControls(); ldapAuthResult.setAuthenticated(true); ldapAuthResult.setResponseControl(responseControls); } catch (Exception e) { if (_log.isDebugEnabled()) { _log.debug( "Failed to bind to the LDAP server with userDN " + userDN + " and password " + password, e); } ldapAuthResult.setAuthenticated(false); ldapAuthResult.setErrorMessage(e.getMessage()); setFailedLDAPAuthResult(env, ldapAuthResult); } finally { if (initialLdapContext != null) { initialLdapContext.close(); } } } else if (authMethod.equals( LDAPConstants.AUTH_METHOD_PASSWORD_COMPARE)) { ldapAuthResult = new LDAPAuthResult(); Attribute userPassword = attributes.get("userPassword"); if (userPassword != null) { String ldapPassword = new String((byte[])userPassword.get()); String encryptedPassword = removeEncryptionAlgorithm( ldapPassword); String algorithm = ldapAuthConfiguration.passwordEncryptionAlgorithm(); if (Validator.isNotNull(algorithm)) { encryptedPassword = _passwordEncryptor.encrypt( algorithm, password, ldapPassword); } if (ldapPassword.equals(encryptedPassword)) { ldapAuthResult.setAuthenticated(true); } else { ldapAuthResult.setAuthenticated(false); if (_log.isDebugEnabled()) { _log.debug( "Passwords do not match for userDN " + userDN); } } } } return ldapAuthResult; } protected int authenticate( long ldapServerId, long companyId, String emailAddress, String screenName, long userId, String password) throws Exception { LdapContext ldapContext = _portalLDAP.getContext( ldapServerId, companyId); if (ldapContext == null) { if (_log.isDebugEnabled()) { _log.debug( "No LDAP server configuration available for LDAP server " + ldapServerId + " and company " + companyId); } return FAILURE; } NamingEnumeration<SearchResult> enu = null; try { LDAPServerConfiguration ldapServerConfiguration = _ldapServerConfigurationProvider.getConfiguration( companyId, ldapServerId); String baseDN = ldapServerConfiguration.baseDN(); // Process LDAP auth search filter String filter = _ldapSettings.getAuthSearchFilter( ldapServerId, companyId, emailAddress, screenName, String.valueOf(userId)); Properties userMappings = _ldapSettings.getUserMappings( ldapServerId, companyId); String userMappingsScreenName = GetterUtil.getString( userMappings.getProperty("screenName")); userMappingsScreenName = StringUtil.toLowerCase( userMappingsScreenName); SearchControls searchControls = new SearchControls( SearchControls.SUBTREE_SCOPE, 1, 0, new String[] {userMappingsScreenName}, false, false); enu = ldapContext.search(baseDN, filter, searchControls); if (!enu.hasMoreElements()) { if (_log.isDebugEnabled()) { _log.debug( "No results found with search filter: " + filter); } return DNE; } if (_log.isDebugEnabled()) { _log.debug("Found results with search filter: " + filter); } SearchResult result = enu.nextElement(); String fullUserDN = _portalLDAP.getNameInNamespace( ldapServerId, companyId, result); Attributes attributes = _portalLDAP.getUserAttributes( ldapServerId, companyId, ldapContext, fullUserDN); // Authenticate LDAPAuthResult ldapAuthResult = authenticate( ldapContext, companyId, attributes, fullUserDN, password); // Get user or create from LDAP if (!ldapAuthResult.isAuthenticated()) { password = null; } User user = _ldapUserImporter.importUser( ldapServerId, companyId, ldapContext, attributes, password); // Process LDAP failure codes String errorMessage = ldapAuthResult.getErrorMessage(); if (errorMessage != null) { SystemLDAPConfiguration systemLDAPConfiguration = _systemLDAPConfigurationProvider.getConfiguration( companyId); for (String errorUserLockoutKeyword : systemLDAPConfiguration.errorUserLockoutKeywords()) { if (errorMessage.contains(errorUserLockoutKeyword)) { throw new UserLockoutException.LDAPLockout( fullUserDN, errorMessage); } } for (String errorPasswordExpiredKeyword : systemLDAPConfiguration. errorPasswordExpiredKeywords()) { if (errorMessage.contains(errorPasswordExpiredKeyword)) { throw new PasswordExpiredException(); } } } if (!ldapAuthResult.isAuthenticated()) { if (_log.isDebugEnabled()) { StringBundler sb = new StringBundler(10); sb.append("Uanble to authenticate with "); sb.append(fullUserDN); sb.append(" on LDAP server "); sb.append(ldapServerId); sb.append(", company "); sb.append(companyId); sb.append(", and LDAP context "); sb.append(ldapContext); sb.append(": "); sb.append(errorMessage); _log.debug(sb.toString()); } return FAILURE; } // Process LDAP success codes String resultCode = ldapAuthResult.getResponseControl(); if (resultCode.equals(LDAPAuth.RESULT_PASSWORD_RESET)) { _userLocalService.updatePasswordReset(user.getUserId(), true); } } catch (Exception e) { if (e instanceof PasswordExpiredException || e instanceof UserLockoutException) { throw e; } _log.error("Problem accessing LDAP server", e); return FAILURE; } finally { if (enu != null) { enu.close(); } ldapContext.close(); } return SUCCESS; } protected int authenticate( long companyId, String emailAddress, String screenName, long userId, String password) throws Exception { LDAPAuthConfiguration ldapAuthConfiguration = _ldapAuthConfigurationProvider.getConfiguration(companyId); if (!ldapAuthConfiguration.enabled()) { if (_log.isDebugEnabled()) { _log.debug("Authenticator is not enabled"); } return SUCCESS; } if (_log.isDebugEnabled()) { _log.debug("Authenticator is enabled"); } long preferredLDAPServerId = getPreferredLDAPServer( companyId, emailAddress, screenName, userId); int preferredLDAPServerResult = authenticateAgainstPreferredLDAPServer( companyId, preferredLDAPServerId, emailAddress, screenName, userId, password); LDAPImportConfiguration ldapImportConfiguration = _ldapImportConfigurationProvider.getConfiguration(companyId); if (preferredLDAPServerResult == SUCCESS) { if (_log.isDebugEnabled()) { _log.debug("Found preferred LDAP server"); } if (ldapImportConfiguration.importUserPasswordEnabled()) { if (_log.isDebugEnabled()) { _log.debug("Import user password enabled"); } return preferredLDAPServerResult; } if (_log.isDebugEnabled()) { _log.debug("Import user password disabled"); } return Authenticator.SKIP_LIFERAY_CHECK; } List<LDAPServerConfiguration> ldapServerConfigurations = _ldapServerConfigurationProvider.getConfigurations(companyId); for (LDAPServerConfiguration ldapServerConfiguration : ldapServerConfigurations) { if (preferredLDAPServerId == ldapServerConfiguration.ldapServerId()) { if (_log.isDebugEnabled()) { _log.debug("Bypassing preferred LDAP server"); } continue; } int result = authenticate( ldapServerConfiguration.ldapServerId(), companyId, emailAddress, screenName, userId, password); if (result == SUCCESS) { if (ldapImportConfiguration.importUserPasswordEnabled()) { return result; } return Authenticator.SKIP_LIFERAY_CHECK; } } return authenticateRequired( companyId, userId, emailAddress, screenName, true, FAILURE); } protected int authenticateAgainstPreferredLDAPServer( long companyId, long ldapServerId, String emailAddress, String screenName, long userId, String password) throws Exception { int result = DNE; if (ldapServerId < 0) { return result; } LDAPServerConfiguration ldapServerConfiguration = _ldapServerConfigurationProvider.getConfiguration( companyId, ldapServerId); String providerUrl = ldapServerConfiguration.baseProviderURL(); if (Validator.isNull(providerUrl)) { return result; } result = authenticate( ldapServerId, companyId, emailAddress, screenName, userId, password); return result; } protected int authenticateOmniadmin( long companyId, String emailAddress, String screenName, long userId) throws Exception { // Only allow omniadmin if Liferay password checking is enabled if (!_authPipelineEnableLiferayCheck) { return FAILURE; } if (userId > 0) { if (_omniadmin.isOmniadmin(userId)) { return SUCCESS; } } else if (Validator.isNotNull(emailAddress)) { User user = _userLocalService.fetchUserByEmailAddress( companyId, emailAddress); if (user != null) { if (_omniadmin.isOmniadmin(user)) { return SUCCESS; } } } else if (Validator.isNotNull(screenName)) { User user = _userLocalService.fetchUserByScreenName( companyId, screenName); if (user != null) { if (_omniadmin.isOmniadmin(user)) { return SUCCESS; } } } return FAILURE; } protected int authenticateRequired( long companyId, long userId, String emailAddress, String screenName, boolean allowOmniadmin, int failureCode) throws Exception { // Make exceptions for omniadmins so that if they break the LDAP // configuration, they can still login to fix the problem if (allowOmniadmin && (authenticateOmniadmin( companyId, emailAddress, screenName, userId) == SUCCESS)) { return SUCCESS; } LDAPAuthConfiguration ldapAuthConfiguration = _ldapAuthConfigurationProvider.getConfiguration(companyId); if (ldapAuthConfiguration.required()) { return failureCode; } else { return SUCCESS; } } protected LDAPAuthResult getFailedLDAPAuthResult(Map<String, Object> env) { Map<String, LDAPAuthResult> failedLDAPAuthResults = _failedLDAPAuthResults.get(); String cacheKey = getKey(env); return failedLDAPAuthResults.get(cacheKey); } protected String getKey(Map<String, Object> env) { StringBundler sb = new StringBundler(5); sb.append(MapUtil.getString(env, Context.PROVIDER_URL)); sb.append(StringPool.POUND); sb.append(MapUtil.getString(env, Context.SECURITY_PRINCIPAL)); sb.append(StringPool.POUND); sb.append(MapUtil.getString(env, Context.SECURITY_CREDENTIALS)); return sb.toString(); } protected long getPreferredLDAPServer( long companyId, String emailAddress, String screenName, long userId) throws PortalException { User user = null; if (userId > 0) { user = _userLocalService.fetchUserById(userId); } else if (Validator.isNotNull(emailAddress)) { user = _userLocalService.fetchUserByEmailAddress( companyId, emailAddress); } else if (Validator.isNotNull(screenName)) { user = _userLocalService.fetchUserByScreenName( companyId, screenName); } else { if (_log.isDebugEnabled()) { _log.debug("Unable to get preferred LDAP server"); } return -1; } if (user == null) { if (_log.isDebugEnabled()) { _log.debug("Unable to get user " + userId); } return -1; } if (_log.isDebugEnabled()) { _log.debug( "Using LDAP server " + user.getLdapServerId() + " to authenticate user " + userId); } return user.getLdapServerId(); } protected String removeEncryptionAlgorithm(String ldapPassword) { if (_log.isDebugEnabled()) { _log.debug("Removing encryption algorithm"); } int x = ldapPassword.indexOf(StringPool.OPEN_CURLY_BRACE); if (x == -1) { return ldapPassword; } int y = ldapPassword.indexOf(StringPool.CLOSE_CURLY_BRACE); if (y == -1) { return ldapPassword; } return ldapPassword.substring(x, y + 1); } @Reference( target = "(factoryPid=com.liferay.portal.security.ldap.authenticator.configuration.LDAPAuthConfiguration)", unbind = "-" ) protected void setConfigurationProvider( ConfigurationProvider<LDAPAuthConfiguration> ldapAuthConfigurationProvider) { _ldapAuthConfigurationProvider = ldapAuthConfigurationProvider; } protected void setFailedLDAPAuthResult( Map<String, Object> env, LDAPAuthResult ldapAuthResult) { Map<String, LDAPAuthResult> failedLDAPAuthResults = _failedLDAPAuthResults.get(); String cacheKey = getKey(env); if (failedLDAPAuthResults.containsKey(cacheKey)) { return; } failedLDAPAuthResults.put(cacheKey, ldapAuthResult); } @Reference( target = "(factoryPid=com.liferay.portal.security.ldap.exportimport.configuration.LDAPImportConfiguration)", unbind = "-" ) protected void setLDAPImportConfigurationProvider( ConfigurationProvider<LDAPImportConfiguration> ldapImportConfigurationProvider) { _ldapImportConfigurationProvider = ldapImportConfigurationProvider; } @Reference( target = "(factoryPid=com.liferay.portal.security.ldap.configuration.LDAPServerConfiguration)", unbind = "-" ) protected void setLDAPServerConfigurationProvider( ConfigurationProvider<LDAPServerConfiguration> ldapServerConfigurationProvider) { _ldapServerConfigurationProvider = ldapServerConfigurationProvider; } @Reference(unbind = "-") protected void setLdapSettings(LDAPSettings ldapSettings) { _ldapSettings = ldapSettings; } @Reference(policyOption = ReferencePolicyOption.GREEDY, unbind = "-") protected void setLdapUserImporter(LDAPUserImporter ldapUserImporter) { _ldapUserImporter = ldapUserImporter; } @Reference(unbind = "-") protected void setOmniadmin(Omniadmin omniadmin) { _omniadmin = omniadmin; } @Reference(unbind = "-") protected void setPasswordEncryptor(PasswordEncryptor passwordEncryptor) { _passwordEncryptor = passwordEncryptor; } @Reference(policyOption = ReferencePolicyOption.GREEDY, unbind = "-") protected void setPortalLDAP(PortalLDAP portalLDAP) { _portalLDAP = portalLDAP; } @Reference(unbind = "-") protected void setProps(Props props) { _props = props; } @Reference( target = "(factoryPid=com.liferay.portal.security.ldap.configuration.SystemLDAPConfiguration)", unbind = "-" ) protected void setSystemLDAPConfigurationProvider( ConfigurationProvider<SystemLDAPConfiguration> systemLDAPConfigurationProvider) { _systemLDAPConfigurationProvider = systemLDAPConfigurationProvider; } @Reference(unbind = "-") protected void setUserLocalService(UserLocalService userLocalService) { _userLocalService = userLocalService; } private static final Log _log = LogFactoryUtil.getLog(LDAPAuth.class); private boolean _authPipelineEnableLiferayCheck; private final ThreadLocal<Map<String, LDAPAuthResult>> _failedLDAPAuthResults = new AutoResetThreadLocal<>( LDAPAuth.class + "._failedLDAPAuthResultCache", HashMap::new); private ConfigurationProvider<LDAPAuthConfiguration> _ldapAuthConfigurationProvider; private ConfigurationProvider<LDAPImportConfiguration> _ldapImportConfigurationProvider; private ConfigurationProvider<LDAPServerConfiguration> _ldapServerConfigurationProvider; private LDAPSettings _ldapSettings; private LDAPUserImporter _ldapUserImporter; private Omniadmin _omniadmin; private PasswordEncryptor _passwordEncryptor; private PortalLDAP _portalLDAP; private Props _props; private ConfigurationProvider<SystemLDAPConfiguration> _systemLDAPConfigurationProvider; private UserLocalService _userLocalService; }