package marubinotto.piggydb.ui.page.common;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import marubinotto.piggydb.model.auth.User;
import marubinotto.piggydb.service.WarSetting;
import marubinotto.piggydb.ui.page.model.SelectedFragments;
import marubinotto.util.Assert;
import marubinotto.util.time.DateTime;
import net.sf.click.Context;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Session {
private static Log logger = LogFactory.getLog(Session.class);
public static final String SK_MESSAGE = "message";
private static final String SK_CLIENT_ADDRESS = "clientAddress";
private static final String SK_USER_AGENT = "userAgent";
private static final String SK_SELECTED_FRAGMENTS = "selectedFragments";
public static final String SK_UI_STATE = "ui-state";
private Context context;
private HttpServletRequest request;
private WarSetting warSetting;
private boolean anonymousEnabled = false;
public Session(Context context, WarSetting warSetting, boolean anonymousEnabled) {
this.context = context;
this.request = this.context.getRequest();
this.warSetting = warSetting;
this.anonymousEnabled = anonymousEnabled;
}
@SuppressWarnings("rawtypes")
public void log() {
if (!logger.isDebugEnabled()) return;
HttpSession session = getHttpSessionIfExists();
if (session == null) {
logger.debug("No session.");
return;
}
logger.debug("Session {");
for (Enumeration e = session.getAttributeNames(); e.hasMoreElements();) {
String name = (String)e.nextElement();
logger.debug(" " + name + " => " + session.getAttribute(name));
}
logger.debug("}");
}
public void start(User user, boolean rememberMe) {
Assert.Arg.notNull(user, "user");
HttpSession newSession = this.request.getSession(true);
newSession.setAttribute(User.KEY, user);
newSession.setAttribute(SK_CLIENT_ADDRESS, this.request.getRemoteAddr());
newSession.setAttribute(SK_USER_AGENT, this.request.getHeader("User-Agent"));
if (rememberMe) {
storeSession(newSession);
user.setSessionPersisted(true);
logger.debug("Set the session persisted");
}
}
public User getUser() {
if (!this.context.hasSession()) {
return null;
}
// Avoid the session hijacking
if (!validateRemoteAddress()) {
this.context.getSession().invalidate();
logger.warn("Session invalidated for an invalid client address");
return null;
}
if (!validateUserAgent()) {
this.context.getSession().invalidate();
logger.warn("Session invalidated for an invalid user agent");
return null;
}
// Get the user object in this session
User user = (User)this.context.getSessionAttribute(User.KEY);
if (user == null) return null;
// While a session is persisted, the setting of anonymous access could be changed
if (!this.anonymousEnabled && user.isAnonymous()) {
this.context.getSession().invalidate();
logger.warn("Invalid anonymous session invalidated");
return null;
}
if (user.hasSessionPersisted()) {
setSessionCookieWhenPersistentCookieIsAboutToBeExpired(this.context.getSession());
}
return user;
}
public void invalidateIfExists() {
HttpSession httpSession = getHttpSessionIfExists();
if (httpSession != null) httpSession.invalidate();
}
protected HttpSession getHttpSessionIfExists() {
return this.request.getSession(false);
}
private boolean validateRemoteAddress() {
if (!this.warSetting.isClientAddressAuthEnabled()) {
return true;
}
String expected = (String)this.context.getSessionAttribute(SK_CLIENT_ADDRESS);
String actual = this.request.getRemoteAddr();
logger.info("Validate the remote address: " + actual + " (expected: " + expected + ")");
return actual.equals(expected);
}
private boolean validateUserAgent() {
if (!this.warSetting.isUserAgentAuthEnabled()) {
return true;
}
String expected = (String)this.context.getSessionAttribute(SK_USER_AGENT);
String actual = this.request.getHeader("User-Agent");
logger.info("Validate the user agent: " + actual + " (expected: " + expected + ")");
return ObjectUtils.equals(actual, expected);
}
// Message
public void setFlashMessage(String message) {
this.context.setFlashAttribute(SK_MESSAGE, message);
}
public String getMessage() {
return (String)this.context.getSessionAttribute(SK_MESSAGE);
}
// Session objects
@SuppressWarnings("unchecked")
public <T> T createOrGet(String name, Factory<T> factory) {
Assert.Arg.notNull(name, "name");
Assert.Arg.notNull(factory, "factory");
T object = (T)this.context.getSessionAttribute(name);
if (object == null) {
object = factory.create();
this.context.setSessionAttribute(name, object);
}
return object;
}
public static interface Factory<T> {
public T create();
}
public SelectedFragments getSelectedFragments() {
return createOrGet(
SK_SELECTED_FRAGMENTS,
new Factory<SelectedFragments>() {
public SelectedFragments create() {
return new SelectedFragments();
}
});
}
public Map<String, Object> getUiState() {
return createOrGet(
SK_UI_STATE,
new Factory<Map<String, Object>>() {
public Map<String, Object> create() {
return new HashMap<String, Object>();
}
});
}
// Session persistence
private static final int PERSISTED_SESSION_MAX_AGE = 60 * 60 * 24 * 7 * 2; // 2 weeks
private static final String COOKIE_NAME_SESSION_ID = "JSESSIONID";
private void storeSession(HttpSession session) {
session.setMaxInactiveInterval(PERSISTED_SESSION_MAX_AGE);
Cookie cookie = createSessionCookie(session);
cookie.setMaxAge(PERSISTED_SESSION_MAX_AGE);
this.context.getResponse().addCookie(cookie);
}
private Cookie createSessionCookie(HttpSession session) {
Cookie cookie = new Cookie(COOKIE_NAME_SESSION_ID, session.getId());
cookie.setPath(this.request.getContextPath());
return cookie;
}
private static final long THRESHOLD_TO_BE_EXPIRED = 1000 * 60 * 60; // 1 hour
private void setSessionCookieWhenPersistentCookieIsAboutToBeExpired(
HttpSession session) {
if (isPersistentCookieAboutToBeExpired(session)) {
this.context.getResponse().addCookie(createSessionCookie(session));
logger.debug("The persistent Cookie to be overwritten with a session cookie.");
}
}
private static boolean isPersistentCookieAboutToBeExpired(HttpSession session) {
long sessionAge = DateTime.getCurrentTime().getTime() - session.getCreationTime();
long left = (PERSISTED_SESSION_MAX_AGE * 1000) - sessionAge;
return left < THRESHOLD_TO_BE_EXPIRED;
}
}