/*
* 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.http.state;
import password.pwm.AppProperty;
import password.pwm.PwmApplication;
import password.pwm.bean.LoginInfoBean;
import password.pwm.bean.UserIdentity;
import password.pwm.config.PwmSetting;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmException;
import password.pwm.error.PwmOperationalException;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.IdleTimeoutCalculator;
import password.pwm.http.PwmHttpResponseWrapper;
import password.pwm.http.PwmRequest;
import password.pwm.http.PwmResponse;
import password.pwm.ldap.auth.AuthenticationType;
import password.pwm.ldap.auth.SessionAuthenticator;
import password.pwm.svc.stats.Statistic;
import password.pwm.svc.stats.StatisticsManager;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
class CryptoCookieLoginImpl implements SessionLoginProvider {
private static final PwmLogger LOGGER = PwmLogger.forClass(CryptoCookieLoginImpl.class);
private static final PwmResponse.CookiePath COOKIE_PATH = PwmHttpResponseWrapper.CookiePath.Application;
private String cookieName = "SESSION";
@Override
public void init(final PwmApplication pwmApplication) throws PwmException {
cookieName = pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_COOKIE_LOGIN_NAME);
}
@Override
public void clearLoginSession(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
pwmRequest.getPwmResponse().removeCookie(cookieName, COOKIE_PATH);
}
@Override
public void saveLoginSessionState(final PwmRequest pwmRequest) {
try {
final LoginInfoBean loginInfoBean = pwmRequest.getPwmSession().getLoginInfoBean();
loginInfoBean.setReqTime(Instant.now());
pwmRequest.getPwmResponse().writeEncryptedCookie(
cookieName,
loginInfoBean,
COOKIE_PATH
);
LOGGER.trace(pwmRequest, "wrote LoginInfoBean=" + loginInfoBean.toDebugString());
} catch (PwmUnrecoverableException e) {
final String errorMsg = "unexpected error writing login cookie to response: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg);
LOGGER.error(pwmRequest, errorInformation);
}
}
@Override
public void readLoginSessionState(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
final LoginInfoBean remoteLoginCookie;
try {
remoteLoginCookie = pwmRequest.readEncryptedCookie(cookieName, LoginInfoBean.class);
} catch (PwmUnrecoverableException e) {
final String errorMsg = "unexpected error reading login cookie, will clear and ignore; error: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg);
LOGGER.error(pwmRequest, errorInformation);
clearLoginSession(pwmRequest);
return;
}
if (remoteLoginCookie != null) {
try {
try {
checkIfRemoteLoginCookieIsValid(pwmRequest, remoteLoginCookie);
} catch (PwmOperationalException e) {
LOGGER.debug(pwmRequest, e.getErrorInformation().toDebugStr());
clearLoginSession(pwmRequest);
return;
}
checkIfLoginCookieIsForeign(pwmRequest, remoteLoginCookie);
importRemoteCookie(pwmRequest, remoteLoginCookie);
} catch (Exception e) {
final String errorMsg = "unexpected error authenticating using crypto session cookie: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
LOGGER.error(pwmRequest, errorInformation);
throw new PwmUnrecoverableException(errorInformation);
}
}
}
private static void importRemoteCookie(
final PwmRequest pwmRequest,
final LoginInfoBean remoteLoginCookie
) throws PwmUnrecoverableException
{
if (remoteLoginCookie == null) {
return;
}
final LoginInfoBean localLoginCookie = pwmRequest.getPwmSession().getLoginInfoBean();
if (remoteLoginCookie.isAuthenticated()) {
if (localLoginCookie.isAuthenticated()) {
// should never get here unless one of container session and app session key are swapped between users.
final UserIdentity remoteIdentity = remoteLoginCookie.getUserIdentity();
final UserIdentity localIdentity = localLoginCookie.getUserIdentity();
if (remoteIdentity != null && localIdentity != null && !remoteIdentity.equals(localIdentity)) {
throw new PwmUnrecoverableException(
new ErrorInformation(PwmError.ERROR_BAD_SESSION,"remote and local session identities differ")
);
}
} else {
LOGGER.debug(pwmRequest, "triggering authentication because request contains an authenticated session but local session is unauthenticated");
final SessionAuthenticator sessionAuthenticator = new SessionAuthenticator(
pwmRequest.getPwmApplication(),
pwmRequest.getPwmSession(),
remoteLoginCookie.getAuthSource()
);
try {
if (remoteLoginCookie.getUserIdentity() == null) {
sessionAuthenticator.authUserWithUnknownPassword(
remoteLoginCookie.getUserIdentity(),
remoteLoginCookie.getType()
);
} else {
sessionAuthenticator.authenticateUser(
remoteLoginCookie.getUserIdentity(),
remoteLoginCookie.getUserCurrentPassword()
);
}
remoteLoginCookie.getAuthFlags().add(AuthenticationType.AUTH_FROM_REQ_COOKIE);
LOGGER.debug(pwmRequest, "logged in using encrypted request cookie = " + JsonUtil.serialize(remoteLoginCookie));
} catch (Exception e) {
final String errorMsg = "unexpected error reading session cookie: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN, errorMsg);
LOGGER.error(pwmRequest, errorInformation);
throw new PwmUnrecoverableException(errorInformation);
}
}
}
if (pwmRequest.getConfig().isDevDebugMode()) {
LOGGER.trace(pwmRequest, "imported LoginInfoBean=" + remoteLoginCookie.toDebugString());
}
pwmRequest.getPwmSession().setLoginInfoBean(remoteLoginCookie);
}
private static void checkIfRemoteLoginCookieIsValid(
final PwmRequest pwmRequest,
final LoginInfoBean loginInfoBean
)
throws PwmOperationalException
{
if (loginInfoBean.isAuthenticated() && loginInfoBean.getAuthTime() == null) {
final String errorMsg = "decrypted login cookie does not specify a local auth time";
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_BAD_SESSION, errorMsg);
throw new PwmOperationalException(errorInformation);
}
if (loginInfoBean.getAuthTime() != null) {
final long sessionMaxSeconds = pwmRequest.getConfig().readSettingAsLong(PwmSetting.SESSION_MAX_SECONDS);
final TimeDuration sessionTotalAge = TimeDuration.fromCurrent(loginInfoBean.getAuthTime());
final TimeDuration sessionMaxAge = new TimeDuration(sessionMaxSeconds, TimeUnit.SECONDS);
if (sessionTotalAge.isLongerThan(sessionMaxAge)) {
final String errorMsg = "decrypted login cookie age ("
+ sessionTotalAge.asCompactString()
+ ") is older than max session seconds ("
+ sessionMaxAge.asCompactString()
+ ")";
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_BAD_SESSION, errorMsg);
throw new PwmOperationalException(errorInformation);
}
}
if (loginInfoBean.getReqTime() == null) {
final String errorMsg = "decrypted login cookie does not specify a issue time";
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_BAD_SESSION, errorMsg);
throw new PwmOperationalException(errorInformation);
}
{
final TimeDuration loginCookieIssueAge = TimeDuration.fromCurrent(loginInfoBean.getReqTime());
final TimeDuration maxIdleDuration = IdleTimeoutCalculator.idleTimeoutForRequest(pwmRequest);
if (loginCookieIssueAge.isLongerThan(maxIdleDuration)) {
final String errorMsg = "decrypted login cookie issue time ("
+ loginCookieIssueAge.asCompactString()
+ ") is older than max idle seconds ("
+ maxIdleDuration.asCompactString()
+ ")";
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_BAD_SESSION, errorMsg);
throw new PwmOperationalException(errorInformation);
}
}
}
private static void checkIfLoginCookieIsForeign(final PwmRequest pwmRequest, final LoginInfoBean remoteLoginInfoBean) throws PwmUnrecoverableException {
final String remoteGuid = remoteLoginInfoBean.getGuid();
final String localGuid = pwmRequest.getPwmSession().getLoginInfoBean().getGuid();
if (remoteGuid != null && !remoteGuid.equals(localGuid)) {
final String logMsg = "login cookie session was generated by a foreign instance, seen login cookie value = "
+ remoteLoginInfoBean.toDebugString();
StatisticsManager.incrementStat(pwmRequest.getPwmApplication(), Statistic.FOREIGN_SESSIONS_ACCEPTED);
LOGGER.trace(pwmRequest, logMsg);
}
}
}