/* * 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.auth; import com.google.gson.reflect.TypeToken; import com.novell.ldapchai.ChaiConstant; import com.novell.ldapchai.exception.ChaiError; import com.novell.ldapchai.exception.ChaiException; import com.novell.ldapchai.exception.ChaiUnavailableException; import com.novell.ldapchai.exception.ImpossiblePasswordPolicyException; import com.novell.ldapchai.provider.ChaiProvider; import password.pwm.AppProperty; import password.pwm.PwmApplication; import password.pwm.PwmConstants; import password.pwm.bean.LocalSessionStateBean; import password.pwm.bean.LoginInfoBean; import password.pwm.bean.SessionLabel; import password.pwm.bean.UserIdentity; import password.pwm.bean.UserInfoBean; import password.pwm.config.PwmSetting; 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.LdapOperationsHelper; import password.pwm.ldap.search.UserSearchEngine; import password.pwm.ldap.UserStatusReader; import password.pwm.svc.intruder.IntruderManager; import password.pwm.svc.intruder.RecordType; 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.logging.PwmLogger; import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Set; public class SessionAuthenticator { private static final PwmLogger LOGGER = PwmLogger.getLogger(SessionAuthenticator.class.getName()); private final PwmApplication pwmApplication; private final SessionLabel sessionLabel; private final PwmSession pwmSession; private final PwmAuthenticationSource authenticationSource; public SessionAuthenticator( final PwmApplication pwmApplication, final PwmSession pwmSession, final PwmAuthenticationSource authenticationSource ) { this.pwmApplication = pwmApplication; this.pwmSession = pwmSession; this.sessionLabel = pwmSession.getLabel(); this.authenticationSource = authenticationSource; } public void searchAndAuthenticateUser( final String username, final PasswordData password, final String context, final String ldapProfile ) throws ChaiUnavailableException, PwmUnrecoverableException, PwmOperationalException { pwmApplication.getIntruderManager().check(RecordType.USERNAME, username); UserIdentity userIdentity = null; try { final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine(); userIdentity = userSearchEngine.resolveUsername(username, context, ldapProfile, sessionLabel); final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest( pwmApplication, sessionLabel, userIdentity, AuthenticationType.AUTHENTICATED, authenticationSource ); final AuthenticationResult authResult = authEngine.authenticateUser(password); postAuthenticationSequence(userIdentity, authResult); } catch (PwmOperationalException e) { postFailureSequence(e, username, userIdentity); if (readHiddenErrorTypes().contains(e.getError())) { if (pwmApplication.determineIfDetailErrorMsgShown()) { LOGGER.debug(pwmSession, "allowing error " + e.getError() + " to be returned though it is configured as a hidden type; " + "app is currently permitting detailed error messages"); } else { final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_WRONGPASSWORD); LOGGER.debug(pwmSession, "converting error from ldap " + e.getError() + " to " + PwmError.ERROR_WRONGPASSWORD + " due to app property " + AppProperty.SECURITY_LOGIN_HIDDEN_ERROR_TYPES.getKey()); throw new PwmOperationalException(errorInformation); } } throw e; } } private Set<PwmError> readHiddenErrorTypes() { final String appProperty = pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_LOGIN_HIDDEN_ERROR_TYPES); final Set<PwmError> returnSet = new HashSet<>(); if (!StringUtil.isEmpty(appProperty)) { try { final List<Integer> configuredNumbers = JsonUtil.deserialize(appProperty, new TypeToken<List<Integer>>() { }); for (final Integer errorCode : configuredNumbers) { final PwmError pwmError = PwmError.forErrorNumber(errorCode); returnSet.add(pwmError); } } catch (Exception e) { LOGGER.error(pwmSession, "error parsing app property " + AppProperty.SECURITY_LOGIN_HIDDEN_ERROR_TYPES.getKey() + ", error: " + e.getMessage()); } } return returnSet; } public void authenticateUser( final UserIdentity userIdentity, final PasswordData password ) throws PwmUnrecoverableException, PwmOperationalException { try { final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest( pwmApplication, sessionLabel, userIdentity, AuthenticationType.AUTHENTICATED, authenticationSource ); final AuthenticationResult authResult = authEngine.authenticateUser(password); postAuthenticationSequence(userIdentity, authResult); } catch (ChaiUnavailableException e) { throw PwmUnrecoverableException.fromChaiException(e); } catch (PwmOperationalException e) { postFailureSequence(e, null, userIdentity); throw e; } } public void authUserWithUnknownPassword( final String username, final AuthenticationType requestedAuthType ) throws ImpossiblePasswordPolicyException, PwmUnrecoverableException, PwmOperationalException { pwmApplication.getIntruderManager().check(RecordType.USERNAME, username); UserIdentity userIdentity = null; try { final UserSearchEngine userSearchEngine = pwmApplication.getUserSearchEngine(); userIdentity = userSearchEngine.resolveUsername(username, null, null, sessionLabel); final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest( pwmApplication, sessionLabel, userIdentity, requestedAuthType, authenticationSource ); final AuthenticationResult authResult = authEngine.authUsingUnknownPw(); postAuthenticationSequence(userIdentity, authResult); } catch (ChaiUnavailableException e) { throw PwmUnrecoverableException.fromChaiException(e); } catch (PwmOperationalException e) { postFailureSequence(e, username, userIdentity); throw e; } } public void authUserWithUnknownPassword( final UserIdentity userIdentity, final AuthenticationType requestedAuthType ) throws PwmUnrecoverableException { try { final AuthenticationRequest authEngine = LDAPAuthenticationRequest.createLDAPAuthenticationRequest( pwmApplication, sessionLabel, userIdentity, requestedAuthType, authenticationSource ); final AuthenticationResult authResult = authEngine.authUsingUnknownPw(); postAuthenticationSequence(userIdentity, authResult); } catch (ChaiUnavailableException e) { throw PwmUnrecoverableException.fromChaiException(e); } } public void simulateBadPassword( final UserIdentity userIdentity ) throws PwmUnrecoverableException { if (!pwmApplication.getConfig().readSettingAsBoolean(PwmSetting.SECURITY_SIMULATE_LDAP_BAD_PASSWORD)) { return; } else { LOGGER.trace(sessionLabel, "performing bad-password login attempt against ldap directory as a result of forgotten password recovery invalid attempt against " + userIdentity); } if (userIdentity == null || userIdentity.getUserDN() == null || userIdentity.getUserDN().length() < 1) { LOGGER.error(sessionLabel, "attempt to simulateBadPassword with null userDN"); return; } LOGGER.trace(sessionLabel, "beginning simulateBadPassword process"); final PasswordData bogusPassword = new PasswordData(PwmConstants.DEFAULT_BAD_PASSWORD_ATTEMPT); //try authenticating the user using a normal ldap BIND operation. LOGGER.trace(sessionLabel, "attempting authentication using ldap BIND"); ChaiProvider provider = null; try { //read a provider using the user's DN and password. provider = LdapOperationsHelper.createChaiProvider( sessionLabel, userIdentity.getLdapProfile(pwmApplication.getConfig()), pwmApplication.getConfig(), userIdentity.getUserDN(), bogusPassword ); //issue a read operation to trigger a bind. provider.readStringAttribute(userIdentity.getUserDN(), ChaiConstant.ATTR_LDAP_OBJECTCLASS); LOGGER.debug(sessionLabel, "bad-password login attempt succeeded for " + userIdentity); } catch (ChaiException e) { if (e.getErrorCode() == ChaiError.PASSWORD_BADPASSWORD) { LOGGER.trace(sessionLabel, "bad-password login simulation succeeded for; " + userIdentity + " result: " + e.getMessage()); } else { LOGGER.debug(sessionLabel, "unexpected error during simulated bad-password login attempt for " + userIdentity + "; result: " + e.getMessage()); } } finally { if (provider != null){ try { provider.close(); } catch (Throwable e) { LOGGER.error(sessionLabel, "unexpected error closing invalid ldap connection after simulated bad-password failed login attempt: " + e.getMessage()); } } } } private void postFailureSequence( final PwmOperationalException exception, final String username, final UserIdentity userIdentity ) throws PwmUnrecoverableException { LOGGER.error(sessionLabel, "ldap error during search: " + exception.getMessage()); final IntruderManager intruderManager = pwmApplication.getIntruderManager(); if (intruderManager != null) { intruderManager.convenience().markAddressAndSession(pwmSession); if (username != null) { intruderManager.mark(RecordType.USERNAME, username, pwmSession.getLabel()); } if (userIdentity != null) { intruderManager.convenience().markUserIdentity(userIdentity, sessionLabel); } } } private void postAuthenticationSequence( final UserIdentity userIdentity, final AuthenticationResult authenticationResult ) throws PwmUnrecoverableException, ChaiUnavailableException { final IntruderManager intruderManager = pwmApplication.getIntruderManager(); final LocalSessionStateBean ssBean = pwmSession.getSessionStateBean(); final LoginInfoBean loginInfoBean = pwmSession.getLoginInfoBean(); // auth succeed loginInfoBean.setAuthenticated(true); loginInfoBean.setUserIdentity(userIdentity); //update the session connection pwmSession.getSessionManager().setChaiProvider(authenticationResult.getUserProvider()); // update the actor user info bean { final UserInfoBean userInfoBean = pwmSession.getUserInfoBean(); final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, pwmSession.getLabel()); if (authenticationResult.getAuthenticationType() == AuthenticationType.AUTH_BIND_INHIBIT) { userStatusReader.populateUserInfoBean( userInfoBean, ssBean.getLocale(), userIdentity, pwmApplication.getProxyChaiProvider(userIdentity.getLdapProfileID()) ); } else { userStatusReader.populateActorUserInfoBean( pwmSession, userIdentity ); } } //mark the auth time pwmSession.getLoginInfoBean().setAuthTime(Instant.now()); //update the resulting authType pwmSession.getLoginInfoBean().setType(authenticationResult.getAuthenticationType()); pwmSession.getLoginInfoBean().setAuthSource(authenticationSource); // save the password in the login bean final PasswordData userPassword = authenticationResult.getUserPassword(); pwmSession.getLoginInfoBean().setUserCurrentPassword(userPassword); //notify the intruder manager with a successful login intruderManager.clear(RecordType.USERNAME, pwmSession.getUserInfoBean().getUsername()); intruderManager.convenience().clearUserIdentity(userIdentity); intruderManager.convenience().clearAddressAndSession(pwmSession); if (pwmApplication.getStatisticsManager() != null) { final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager(); if (pwmSession.getUserInfoBean().getPasswordState().isWarnPeriod()) { statisticsManager.incrementValue(Statistic.AUTHENTICATION_EXPIRED_WARNING); } else if (pwmSession.getUserInfoBean().getPasswordState().isPreExpired()) { statisticsManager.incrementValue(Statistic.AUTHENTICATION_PRE_EXPIRED); } else if (pwmSession.getUserInfoBean().getPasswordState().isExpired()) { statisticsManager.incrementValue(Statistic.AUTHENTICATION_EXPIRED); } } //clear permission cache - needs rechecking after login LOGGER.debug(pwmSession,"clearing permission cache"); pwmSession.getUserSessionDataCacheBean().clearPermissions(); } }