Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to change session store implementation #1180

Merged
merged 4 commits into from
Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions framework/src/play/mvc/CookieSessionStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package play.mvc;

import play.Play;
import play.exceptions.UnexpectedException;
import play.libs.Crypto;
import play.libs.Time;

import static play.mvc.Scope.*;
import static play.mvc.Scope.Session.TS_KEY;

/**
* Default session store implementation that stores signed data in a cookie
*/
public class CookieSessionStore implements SessionStore {

@Override
public Session restore() {
try {
Session session = new Session();
Http.Cookie cookie = Http.Request.current().cookies.get(COOKIE_PREFIX + "_SESSION");
int duration = Time.parseDuration(COOKIE_EXPIRE);
long expiration = (duration * 1000l);

if (cookie != null && Play.started && cookie.value != null && !cookie.value.trim().equals("")) {
String value = cookie.value;
int firstDashIndex = value.indexOf("-");
if (firstDashIndex > -1) {
String sign = value.substring(0, firstDashIndex);
String data = value.substring(firstDashIndex + 1);
if (CookieDataCodec.safeEquals(sign, Crypto.sign(data, Play.secretKey.getBytes()))) {
CookieDataCodec.decode(session.data, data);
}
}
if (COOKIE_EXPIRE != null) {
// Verify that the session contains a timestamp, and
// that it's not expired
if (!session.contains(TS_KEY)) {
session = new Session();
} else {
if ((Long.parseLong(session.get(TS_KEY))) < System.currentTimeMillis()) {
// Session expired
session = new Session();
}
}
session.put(TS_KEY, System.currentTimeMillis() + expiration);
} else {
// Just restored. Nothing changed. No cookie-expire.
session.changed = false;
}
} else {
// no previous cookie to restore; but we may have to set the
// timestamp in the new cookie
if (COOKIE_EXPIRE != null) {
session.put(TS_KEY, (System.currentTimeMillis() + expiration));
}
}

return session;
} catch (Exception e) {
throw new UnexpectedException("Corrupted HTTP session from " + Http.Request.current().remoteAddress, e);
}
}

@Override
public void save(Session session) {
if (Http.Response.current() == null) {
// Some request like WebSocket don't have any response
return;
}
if (!session.changed && SESSION_SEND_ONLY_IF_CHANGED && COOKIE_EXPIRE == null) {
// Nothing changed and no cookie-expire, consequently send
// nothing back.
return;
}
if (session.isEmpty()) {
// The session is empty: delete the cookie
if (Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_SESSION") || !SESSION_SEND_ONLY_IF_CHANGED) {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", "", null, "/", 0, COOKIE_SECURE, SESSION_HTTPONLY);
}
return;
}
try {
String sessionData = CookieDataCodec.encode(session.data);
String sign = Crypto.sign(sessionData, Play.secretKey.getBytes());
if (COOKIE_EXPIRE == null) {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", null, COOKIE_SECURE,
SESSION_HTTPONLY);
} else {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/",
Time.parseDuration(COOKIE_EXPIRE), COOKIE_SECURE, SESSION_HTTPONLY);
}
} catch (Exception e) {
throw new UnexpectedException("Session serializationProblem", e);
}
}
}
104 changes: 21 additions & 83 deletions framework/src/play/mvc/Scope.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package play.mvc;

import java.lang.annotation.Annotation;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

import play.Logger;
import play.Play;
import play.data.binding.Binder;
Expand All @@ -20,9 +12,12 @@
import play.i18n.Messages;
import play.libs.Codec;
import play.libs.Crypto;
import play.libs.Time;
import play.utils.Utils;

import java.lang.annotation.Annotation;
import java.net.URLEncoder;
import java.util.*;

/**
* All application Scopes
*/
Expand All @@ -37,6 +32,20 @@ public class Scope {
public static final boolean SESSION_SEND_ONLY_IF_CHANGED = Play.configuration
.getProperty("application.session.sendOnlyIfChanged", "false").toLowerCase().equals("true");

public static SessionStore sessionStore = createSessionStore();

private static SessionStore createSessionStore() {
String sessionStoreClass = Play.configuration.getProperty("application.session.storeClass");
if (sessionStoreClass == null) return new CookieSessionStore();
try {
Logger.info("Storing sessions using " + sessionStoreClass);
return (SessionStore) Class.forName(sessionStoreClass).newInstance();
}
catch (Exception e) {
throw new UnexpectedException("Cannot create instance of " + sessionStoreClass, e);
}
}

/**
* Flash scope
*/
Expand Down Expand Up @@ -163,50 +172,7 @@ public static class Session {
static final String TS_KEY = "___TS";

public static Session restore() {
try {
Session session = new Session();
Http.Cookie cookie = Http.Request.current().cookies.get(COOKIE_PREFIX + "_SESSION");
int duration = Time.parseDuration(COOKIE_EXPIRE);
long expiration = (duration * 1000l);

if (cookie != null && Play.started && cookie.value != null && !cookie.value.trim().equals("")) {
String value = cookie.value;
int firstDashIndex = value.indexOf("-");
if (firstDashIndex > -1) {
String sign = value.substring(0, firstDashIndex);
String data = value.substring(firstDashIndex + 1);
if (CookieDataCodec.safeEquals(sign, Crypto.sign(data, Play.secretKey.getBytes()))) {
CookieDataCodec.decode(session.data, data);
}
}
if (COOKIE_EXPIRE != null) {
// Verify that the session contains a timestamp, and
// that it's not expired
if (!session.contains(TS_KEY)) {
session = new Session();
} else {
if ((Long.parseLong(session.get(TS_KEY))) < System.currentTimeMillis()) {
// Session expired
session = new Session();
}
}
session.put(TS_KEY, System.currentTimeMillis() + expiration);
} else {
// Just restored. Nothing changed. No cookie-expire.
session.changed = false;
}
} else {
// no previous cookie to restore; but we may have to set the
// timestamp in the new cookie
if (COOKIE_EXPIRE != null) {
session.put(TS_KEY, (System.currentTimeMillis() + expiration));
}
}

return session;
} catch (Exception e) {
throw new UnexpectedException("Corrupted HTTP session from " + Http.Request.current().remoteAddress, e);
}
return sessionStore.restore();
}

Map<String, String> data = new HashMap<>(); // ThreadLocal access
Expand All @@ -231,7 +197,7 @@ public Map<String, String> all() {

public String getAuthenticityToken() {
if (!data.containsKey(AT_KEY)) {
this.put(AT_KEY, Crypto.sign(UUID.randomUUID().toString()));
this.put(AT_KEY, Crypto.sign(Codec.UUID()));
}
return data.get(AT_KEY);
}
Expand All @@ -241,35 +207,7 @@ void change() {
}

void save() {
if (Http.Response.current() == null) {
// Some request like WebSocket don't have any response
return;
}
if (!changed && SESSION_SEND_ONLY_IF_CHANGED && COOKIE_EXPIRE == null) {
// Nothing changed and no cookie-expire, consequently send
// nothing back.
return;
}
if (isEmpty()) {
// The session is empty: delete the cookie
if (Http.Request.current().cookies.containsKey(COOKIE_PREFIX + "_SESSION") || !SESSION_SEND_ONLY_IF_CHANGED) {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", "", null, "/", 0, COOKIE_SECURE, SESSION_HTTPONLY);
}
return;
}
try {
String sessionData = CookieDataCodec.encode(data);
String sign = Crypto.sign(sessionData, Play.secretKey.getBytes());
if (COOKIE_EXPIRE == null) {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/", null, COOKIE_SECURE,
SESSION_HTTPONLY);
} else {
Http.Response.current().setCookie(COOKIE_PREFIX + "_SESSION", sign + "-" + sessionData, null, "/",
Time.parseDuration(COOKIE_EXPIRE), COOKIE_SECURE, SESSION_HTTPONLY);
}
} catch (Exception e) {
throw new UnexpectedException("Session serializationProblem", e);
}
sessionStore.save(this);
}

public void put(String key, String value) {
Expand Down
9 changes: 9 additions & 0 deletions framework/src/play/mvc/SessionStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package play.mvc;

/**
* Implementations of session storage mechanisms.
*/
public interface SessionStore {
void save(Scope.Session session);
Scope.Session restore();
}