ninja.session.SessionImpl Maven / Gradle / Ivy
/**
* Copyright (C) the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ninja.session;
import com.google.common.collect.ImmutableMap;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import ninja.Context;
import ninja.Cookie;
import ninja.Result;
import ninja.utils.Clock;
import ninja.utils.CookieDataCodec;
import ninja.utils.CookieEncryption;
import ninja.utils.Crypto;
import ninja.utils.NinjaConstant;
import ninja.utils.NinjaProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
public class SessionImpl implements Session {
private final static Logger logger = LoggerFactory.getLogger(SessionImpl.class);
private final Crypto crypto;
private final CookieEncryption encryption;
private final Clock time;
private Long sessionExpireTimeInMs;
private final Long defaultSessionExpireTimeInMs;
private final Boolean sessionSendOnlyIfChanged;
private final Boolean sessionTransferredOverHttpsOnly;
private final Boolean sessionHttpOnly;
private final String applicationCookieDomain;
private final Map data = new HashMap();
/** Has cookie been changed => only send new cookie stuff has been changed */
private boolean sessionDataHasBeenChanged = false;
private final String sessionCookieName;
@Inject
public SessionImpl(Crypto crypto, CookieEncryption encryption, NinjaProperties ninjaProperties, Clock clock) {
this.crypto = crypto;
this.encryption = encryption;
this.time = clock;
// read configuration stuff:
Integer sessionExpireTimeInSeconds = ninjaProperties
.getInteger(NinjaConstant.sessionExpireTimeInSeconds);
if (sessionExpireTimeInSeconds != null) {
this.defaultSessionExpireTimeInMs = sessionExpireTimeInSeconds * 1000L;
} else {
this.defaultSessionExpireTimeInMs = null;
}
this.sessionExpireTimeInMs = defaultSessionExpireTimeInMs;
this.sessionSendOnlyIfChanged = ninjaProperties.getBooleanWithDefault(
NinjaConstant.sessionSendOnlyIfChanged, true);
this.sessionTransferredOverHttpsOnly = ninjaProperties
.getBooleanWithDefault(
NinjaConstant.sessionTransferredOverHttpsOnly, true);
this.sessionHttpOnly = ninjaProperties.getBooleanWithDefault(
NinjaConstant.sessionHttpOnly, true);
this.applicationCookieDomain = ninjaProperties
.get(NinjaConstant.applicationCookieDomain);
String applicationCookiePrefix = ninjaProperties
.getOrDie(NinjaConstant.applicationCookiePrefix);
this.sessionCookieName = applicationCookiePrefix + ninja.utils.NinjaConstant.SESSION_SUFFIX;
}
@Override
public void init(Context context) {
try {
// get the cookie that contains session information:
Cookie cookie = context.getCookie(sessionCookieName);
// check that the cookie is not empty:
if (cookie != null && cookie.getValue() != null && !cookie.getValue().trim().isEmpty()) {
String value = cookie.getValue();
// the first substring until "-" is the sign
String sign = value.substring(0, value.indexOf("-"));
// rest from "-" until the end is the payload of the cookie
String payload = value.substring(value.indexOf("-") + 1);
// check if payload is valid:
if (CookieDataCodec.safeEquals(sign, crypto.signHmacSha1(payload))) {
payload = encryption.decrypt(payload);
CookieDataCodec.decode(data, payload);
}
// If an expiry time was set previously use that instead of the
// default session expire time.
if (data.containsKey(EXPIRY_TIME_KEY)) {
long expiryTime = Long.parseLong(data.get(EXPIRY_TIME_KEY));
if (expiryTime >= 0) {
sessionExpireTimeInMs = expiryTime;
}
}
checkExpire();
}
} catch (UnsupportedEncodingException unsupportedEncodingException) {
logger.error("Encoding exception - this must not happen", unsupportedEncodingException);
}
}
protected boolean shouldExpire() {
if (sessionExpireTimeInMs != null) {
// Make sure session contains valid timestamp
if (!data.containsKey(TIMESTAMP_KEY)) {
return true;
}
long timestamp = Long.parseLong(data.get(TIMESTAMP_KEY));
return (timestamp + sessionExpireTimeInMs < time.currentTimeMillis());
}
return false;
}
@Override
public void setExpiryTime(Long expiryTimeMs) {
if (expiryTimeMs == null) {
data.remove(EXPIRY_TIME_KEY);
sessionExpireTimeInMs = defaultSessionExpireTimeInMs;
sessionDataHasBeenChanged = true;
} else {
data.put(EXPIRY_TIME_KEY, "" + expiryTimeMs);
sessionExpireTimeInMs = expiryTimeMs;
}
if (sessionExpireTimeInMs != null) {
if (!data.containsKey(TIMESTAMP_KEY)) {
data.put(TIMESTAMP_KEY, "" + time.currentTimeMillis());
}
checkExpire();
sessionDataHasBeenChanged = true;
}
}
private void checkExpire() {
if (sessionExpireTimeInMs != null) {
if (shouldExpire()) {
sessionDataHasBeenChanged = true;
data.clear();
} else {
// Everything's alright => prolong session
data.put(TIMESTAMP_KEY, "" + time.currentTimeMillis());
}
}
}
@Override
public String getId() {
if (!data.containsKey(ID_KEY)) {
put(ID_KEY, UUID.randomUUID().toString());
}
return get(ID_KEY);
}
@Override
public Map getData() {
return ImmutableMap.copyOf(data);
}
@Override
public String getAuthenticityToken() {
if (!data.containsKey(AUTHENTICITY_KEY)) {
put(AUTHENTICITY_KEY, UUID.randomUUID().toString());
}
return get(AUTHENTICITY_KEY);
}
@Override
public void save(Context context) {
// Don't save the cookie nothing has changed, and if we're not expiring or
// we are expiring but we're only updating if the session changes
if (!sessionDataHasBeenChanged
&& (sessionExpireTimeInMs == null || sessionSendOnlyIfChanged)) {
// Nothing changed and no cookie-expire, consequently send nothing
// back.
return;
}
if (isEmpty()) {
// It is empty, but there was a session coming in, therefore clear it
if (context.hasCookie(sessionCookieName)) {
Cookie.Builder cookie = createApplicationCookie(
sessionCookieName,
"",
context)
.setMaxAge(0);
context.addCookie(cookie.build());
}
return;
}
// Make sure it has a timestamp, if it needs one
if (sessionExpireTimeInMs != null && !data.containsKey(TIMESTAMP_KEY)) {
data.put(TIMESTAMP_KEY, Long.toString(System.currentTimeMillis()));
}
try {
String sessionData = CookieDataCodec.encode(data);
// first encrypt data and then generate HMAC from encrypted data
// http://crypto.stackexchange.com/questions/202/should-we-mac-then-encrypt-or-encrypt-then-mac
sessionData = encryption.encrypt(sessionData);
String sign = crypto.signHmacSha1(sessionData);
Cookie.Builder cookie = createApplicationCookie(
sessionCookieName,
sign + "-" + sessionData,
context);
if (sessionExpireTimeInMs != null) {
cookie.setMaxAge((int) (sessionExpireTimeInMs / 1000L));
}
context.addCookie(cookie.build());
} catch (UnsupportedEncodingException unsupportedEncodingException) {
logger.error("Encoding exception - this must not happen", unsupportedEncodingException);
throw new RuntimeException(unsupportedEncodingException);
}
}
@Override
public void put(String key, String value) {
// make sure key is valid:
if (key.contains(":")) {
throw new IllegalArgumentException(
"Character ':' is invalid in a session key.");
}
sessionDataHasBeenChanged = true;
if (value == null) {
remove(key);
} else {
data.put(key, value);
}
}
@Override
public String get(String key) {
return data.get(key);
}
@Override
public String remove(String key) {
sessionDataHasBeenChanged = true;
String result = get(key);
data.remove(key);
return result;
}
@Override
public void clear() {
sessionDataHasBeenChanged = true;
data.clear();
}
@Override
public boolean isEmpty() {
int itemsToIgnore = 0;
if (data.containsKey(TIMESTAMP_KEY)) {
itemsToIgnore++;
}
if (data.containsKey(EXPIRY_TIME_KEY)) {
itemsToIgnore++;
}
return (data.isEmpty() || data.size() == itemsToIgnore);
}
private Cookie.Builder createApplicationCookie(
String sessionCookieName,
String value,
Context context) {
Cookie.Builder cookie = Cookie.builder(sessionCookieName, value);
cookie.setPath(context.getContextPath() + "/");
if (applicationCookieDomain != null) {
cookie.setDomain(applicationCookieDomain);
}
if (sessionTransferredOverHttpsOnly != null) {
cookie.setSecure(sessionTransferredOverHttpsOnly);
}
if (sessionHttpOnly != null) {
cookie.setHttpOnly(sessionHttpOnly);
}
return cookie;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy