package io.vertx.ext.web.handler.impl;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.auth.User;
import io.vertx.ext.web.Cookie;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.sstore.SessionStore;
public class SessionHandlerImpl implements SessionHandler {
private static final String SESSION_USER_HOLDER_KEY = "__vertx.userHolder";
private static final Logger log = LoggerFactory.getLogger(SessionHandlerImpl.class);
private final SessionStore sessionStore;
private String sessionCookieName;
private String sessionCookiePath;
private long sessionTimeout;
private boolean nagHttps;
private boolean sessionCookieSecure;
private boolean sessionCookieHttpOnly;
private int minLength;
private AuthProvider authProvider;
public SessionHandlerImpl(String sessionCookieName, String sessionCookiePath, long sessionTimeout, boolean nagHttps,
boolean sessionCookieSecure, boolean sessionCookieHttpOnly, int minLength, SessionStore sessionStore) {
this.sessionCookieName = sessionCookieName;
this.sessionCookiePath = sessionCookiePath;
this.sessionTimeout = sessionTimeout;
this.nagHttps = nagHttps;
this.sessionStore = sessionStore;
this.sessionCookieSecure = sessionCookieSecure;
this.sessionCookieHttpOnly = sessionCookieHttpOnly;
this.minLength = minLength;
}
@Override
public SessionHandler setSessionTimeout(long timeout) {
this.sessionTimeout = timeout;
return this;
}
@Override
public SessionHandler setNagHttps(boolean nag) {
this.nagHttps = nag;
return this;
}
@Override
public SessionHandler setCookieSecureFlag(boolean secure) {
this.sessionCookieSecure = secure;
return this;
}
@Override
public SessionHandler setCookieHttpOnlyFlag(boolean httpOnly) {
this.sessionCookieHttpOnly = httpOnly;
return this;
}
@Override
public SessionHandler setSessionCookieName(String sessionCookieName) {
this.sessionCookieName = sessionCookieName;
return this;
}
@Override
public SessionHandler setSessionCookiePath(String sessionCookiePath) {
this.sessionCookiePath = sessionCookiePath;
return this;
}
@Override
public SessionHandler setMinLength(int minLength) {
this.minLength = minLength;
return this;
}
@Override
public SessionHandler setAuthProvider(AuthProvider authProvider) {
this.authProvider = authProvider;
return this;
}
@Override
public void handle(RoutingContext context) {
if (nagHttps && log.isDebugEnabled()) {
String uri = context.request().absoluteURI();
if (!uri.startsWith("https:")) {
log.debug(
"Using session cookies without https could make you susceptible to session hijacking: " + uri);
}
}
Cookie cookie = context.getCookie(sessionCookieName);
if (cookie != null) {
String sessionID = cookie.getValue();
if (sessionID != null && sessionID.length() > minLength) {
getSession(context.vertx(), sessionID, res -> {
if (res.succeeded()) {
Session session = res.result();
if (session != null) {
context.setSession(session);
if (authProvider != null) {
UserHolder holder = session.get(SESSION_USER_HOLDER_KEY);
if (holder != null) {
User user = null;
RoutingContext prevContext = holder.context;
if (prevContext != null) {
user = prevContext.user();
} else if (holder.user != null) {
user = holder.user;
user.setAuthProvider(authProvider);
holder.context = context;
holder.user = null;
}
holder.context = context;
if (user != null) {
context.setUser(user);
}
}
addStoreSessionHandler(context, holder == null);
} else {
addStoreSessionHandler(context, false);
}
} else {
createNewSession(context);
}
} else {
context.fail(res.cause());
}
context.next();
});
return;
}
}
createNewSession(context);
context.next();
}
private void getSession(Vertx vertx, String sessionID, Handler<AsyncResult<Session>> resultHandler) {
doGetSession(vertx, System.currentTimeMillis(), sessionID, resultHandler);
}
private void doGetSession(Vertx vertx, long startTime, String sessionID,
Handler<AsyncResult<Session>> resultHandler) {
sessionStore.get(sessionID, res -> {
if (res.succeeded()) {
if (res.result() == null) {
long retryTimeout = sessionStore.retryTimeout();
if (retryTimeout > 0 && System.currentTimeMillis() - startTime < retryTimeout) {
vertx.setTimer(5, v -> doGetSession(vertx, startTime, sessionID, resultHandler));
return;
}
}
}
resultHandler.handle(res);
});
}
private void addStoreSessionHandler(RoutingContext context, boolean storeUser) {
context.addHeadersEndHandler(v -> {
Session session = context.session();
if (!session.isDestroyed()) {
final int currentStatusCode = context.response().getStatusCode();
if (currentStatusCode >= 200 && currentStatusCode < 400) {
if (storeUser) {
if (context.user() != null) {
session.put(SESSION_USER_HOLDER_KEY, new UserHolder(context));
}
}
session.setAccessed();
if (session.isRegenerated()) {
final Cookie cookie = context.getCookie(sessionCookieName);
cookie.setValue(session.value()).setPath("/").setSecure(sessionCookieSecure)
.setHttpOnly(sessionCookieHttpOnly);
sessionStore.delete(session.oldId(), delete -> {
if (delete.failed()) {
log.error("Failed to delete previous session", delete.cause());
} else {
sessionStore.put(session, res -> {
if (res.failed()) {
log.error("Failed to store session", res.cause());
}
});
}
});
} else {
sessionStore.put(session, res -> {
if (res.failed()) {
log.error("Failed to store session", res.cause());
}
});
}
} else {
context.removeCookie(sessionCookieName, false);
}
} else {
context.removeCookie(sessionCookieName);
sessionStore.delete(session.id(), res -> {
if (res.failed()) {
log.error("Failed to delete session", res.cause());
}
});
}
});
}
private void createNewSession(RoutingContext context) {
Session session = sessionStore.createSession(sessionTimeout, minLength);
context.setSession(session);
Cookie cookie = Cookie.cookie(sessionCookieName, session.value());
cookie.setPath(sessionCookiePath);
cookie.setSecure(sessionCookieSecure);
cookie.setHttpOnly(sessionCookieHttpOnly);
context.addCookie(cookie);
addStoreSessionHandler(context, authProvider != null);
}
}