/*
* 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;
import com.novell.ldapchai.exception.ChaiUnavailableException;
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.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.http.bean.UserSessionDataCacheBean;
import password.pwm.ldap.UserStatusReader;
import password.pwm.svc.stats.Statistic;
import password.pwm.svc.stats.StatisticsManager;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.LocaleHelper;
import password.pwm.util.java.TimeDuration;
import password.pwm.util.logging.PwmLogger;
import password.pwm.util.secure.PwmRandom;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.time.Instant;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* @author Jason D. Rivard
*/
public class PwmSession implements Serializable {
// ------------------------------ FIELDS ------------------------------
private static final PwmLogger LOGGER = PwmLogger.forClass(PwmSession.class);
private final LocalSessionStateBean sessionStateBean;
private LoginInfoBean loginInfoBean;
private UserInfoBean userInfoBean;
private UserSessionDataCacheBean userSessionDataCacheBean;
private Settings settings = new Settings();
private static final Object CREATION_LOCK = new Object();
private transient SessionManager sessionManager;
public static PwmSession createPwmSession(final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
synchronized (CREATION_LOCK) {
return new PwmSession(pwmApplication);
}
}
private PwmSession(final PwmApplication pwmApplication)
throws PwmUnrecoverableException
{
if (pwmApplication == null) {
throw new IllegalStateException("PwmApplication must be available during session creation");
}
final int sessionValidationKeyLength = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_SESSION_VALIDATION_KEY_LENGTH));
sessionStateBean = new LocalSessionStateBean(sessionValidationKeyLength);
sessionStateBean.regenerateSessionVerificationKey();
this.sessionStateBean.setSessionID(null);
final StatisticsManager statisticsManager = pwmApplication.getStatisticsManager();
if (statisticsManager != null) {
String nextID = pwmApplication.getStatisticsManager().getStatBundleForKey(StatisticsManager.KEY_CUMULATIVE).getStatistic(Statistic.HTTP_SESSIONS);
try {
nextID = new BigInteger(nextID).toString();
} catch (Exception e) { /* ignore */ }
this.getSessionStateBean().setSessionID(nextID);
}
this.sessionStateBean.setSessionLastAccessedTime(Instant.now());
if (pwmApplication.getStatisticsManager() != null) {
pwmApplication.getStatisticsManager().incrementValue(Statistic.HTTP_SESSIONS);
}
pwmApplication.getSessionTrackService().addSessionData(this);
settings.restKeyLength = Integer.parseInt(pwmApplication.getConfig().readAppProperty(AppProperty.SECURITY_WS_REST_CLIENT_KEY_LENGTH));
LOGGER.trace(this,"created new session");
}
public SessionManager getSessionManager() {
if (sessionManager == null) {
sessionManager = new SessionManager(this);
}
return sessionManager;
}
public LocalSessionStateBean getSessionStateBean() {
return sessionStateBean;
}
public UserInfoBean getUserInfoBean() {
if (!isAuthenticated()) {
throw new IllegalStateException("attempt to read user info bean, but session not authenticated");
}
if (userInfoBean == null) {
userInfoBean = new UserInfoBean();
}
return userInfoBean;
}
public LoginInfoBean getLoginInfoBean() {
if (loginInfoBean == null) {
loginInfoBean = new LoginInfoBean();
}
if (loginInfoBean.getGuid() == null) {
loginInfoBean.setGuid((Long.toString(new Date().getTime(),36) + PwmRandom.getInstance().alphaNumericString(64)));
}
return loginInfoBean;
}
public void setLoginInfoBean(final LoginInfoBean loginInfoBean) {
this.loginInfoBean = loginInfoBean;
}
public UserSessionDataCacheBean getUserSessionDataCacheBean() {
if (userSessionDataCacheBean == null) {
userSessionDataCacheBean = new UserSessionDataCacheBean();
}
return userSessionDataCacheBean;
}
public SessionLabel getLabel() {
final LocalSessionStateBean ssBean = this.getSessionStateBean();
final String userID = isAuthenticated() ? this.getUserInfoBean().getUsername() : null;
final UserIdentity userIdentity = isAuthenticated() ? this.getUserInfoBean().getUserIdentity() : null;
return new SessionLabel(ssBean.getSessionID(),userIdentity,userID,ssBean.getSrcAddress(),ssBean.getSrcAddress());
}
/**
* Unauthenticate the pwmSession
*/
public void unauthenticateUser(final PwmRequest pwmRequest) {
final LocalSessionStateBean ssBean = getSessionStateBean();
if (getLoginInfoBean().isAuthenticated()) { // try to tear out a session normally.
getUserSessionDataCacheBean().clearPermissions();
final StringBuilder sb = new StringBuilder();
sb.append("unauthenticate session from ").append(ssBean.getSrcAddress());
if (getUserInfoBean().getUserIdentity() != null) {
sb.append(" (").append(getUserInfoBean().getUserIdentity()).append(")");
}
// mark the session state bean as no longer being authenticated
this.getLoginInfoBean().setAuthenticated(false);
// close out any outstanding connections
getSessionManager().closeConnections();
LOGGER.debug(this, sb.toString());
}
if (pwmRequest != null) {
try {
pwmRequest.getPwmApplication().getSessionStateService().clearLoginSession(pwmRequest);
} catch (PwmUnrecoverableException e) {
final String errorMsg = "unexpected error writing removing login cookie from response: " + e.getMessage();
final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg);
LOGGER.error(pwmRequest, errorInformation);
}
pwmRequest.getHttpServletRequest().setAttribute(PwmConstants.SESSION_ATTR_BEANS,null);
}
userInfoBean = null;
loginInfoBean = null;
userSessionDataCacheBean = null;
}
public TimeDuration getIdleTime() {
return TimeDuration.fromCurrent(sessionStateBean.getSessionLastAccessedTime());
}
public String toString() {
final Map<String,Object> debugData = new LinkedHashMap<>();
try {
debugData.put("sessionID",getSessionStateBean().getSessionID());
debugData.put("auth",this.isAuthenticated());
if (this.isAuthenticated()) {
debugData.put("passwordStatus",getUserInfoBean().getPasswordState());
debugData.put("guid",getUserInfoBean().getUserGuid());
debugData.put("dn",getUserInfoBean().getUserIdentity());
debugData.put("authType",getLoginInfoBean().getType());
debugData.put("needsNewPW",getUserInfoBean().isRequiresNewPassword());
debugData.put("needsNewCR",getUserInfoBean().isRequiresResponseConfig());
debugData.put("needsNewProfile",getUserInfoBean().isRequiresUpdateProfile());
debugData.put("hasCRPolicy",getUserInfoBean().getChallengeProfile() != null && getUserInfoBean().getChallengeProfile().getChallengeSet() != null);
}
debugData.put("locale",getSessionStateBean().getLocale());
debugData.put("theme",getSessionStateBean().getTheme());
} catch (Exception e) {
return "exception generating PwmSession.toString(): " + e.getMessage();
}
return "PwmSession instance: " + JsonUtil.serializeMap(debugData);
}
public boolean setLocale(final PwmApplication pwmApplication, final String localeString)
throws PwmUnrecoverableException
{
if (pwmApplication == null) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE, "unable to read context manager"));
}
final LocalSessionStateBean ssBean = this.getSessionStateBean();
final List<Locale> knownLocales = pwmApplication.getConfig().getKnownLocales();
final Locale requestedLocale = LocaleHelper.parseLocaleString(localeString);
if (knownLocales.contains(requestedLocale) || "default".equalsIgnoreCase(localeString)) {
LOGGER.debug(this, "setting session locale to '" + localeString + "'");
ssBean.setLocale("default".equalsIgnoreCase(localeString)
? PwmConstants.DEFAULT_LOCALE
: requestedLocale);
if (this.isAuthenticated()) {
try {
final UserStatusReader userStatusReader = new UserStatusReader(pwmApplication, this.getLabel());
userStatusReader.populateLocaleSpecificUserInfoBean(this.getUserInfoBean(), ssBean.getLocale());
} catch (ChaiUnavailableException e) {
LOGGER.warn("unable to refresh locale-specific user data, error:" + e.getLocalizedMessage());
}
}
return true;
} else {
LOGGER.error(this, "ignoring unknown locale value set request for locale '" + localeString + "'");
ssBean.setLocale(PwmConstants.DEFAULT_LOCALE);
return false;
}
}
public String getRestClientKey() {
if (!this.isAuthenticated()) {
return "";
}
final String restClientKey = this.getSessionStateBean().getRestClientKey();
if (restClientKey != null && restClientKey.length() > 0) {
return restClientKey;
}
final String newKey = Long.toString(System.currentTimeMillis(),36) + PwmRandom.getInstance().alphaNumericString(settings.restKeyLength);
this.getSessionStateBean().setRestClientKey(newKey);
return newKey;
}
public boolean isAuthenticated() {
return getLoginInfoBean().isAuthenticated();
}
private static class Settings implements Serializable {
private int restKeyLength = 36; // default
}
public int size() {
try {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(this);
return byteArrayOutputStream.size();
} catch (Exception e) {
return 0;
}
}
}