Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,78 @@
package org.entando.entando.plugins.jpredis.aps.system.redis.session;

import jakarta.servlet.ServletContext;
import jakarta.servlet.SessionCookieConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.entando.entando.aps.util.UrlUtils;
import org.entando.entando.plugins.jpredis.aps.system.redis.conditions.RedisSessionActive;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.web.context.ServletContextAware;

@Slf4j
@Configuration
@EnableRedisHttpSession
@RedisSessionActive(true)
public class RedisSessionConfig {
public class RedisSessionConfig implements ServletContextAware {

static final String ENTANDO_SECURE_SECRET_COOKIES = "ENTANDO_SECURE_SECRET_COOKIES";

private ServletContext servletContext;

@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}

@Bean
public CookieSerializer cookieSerializer() {
return createCookieSerializer(isSecureCookie(System.getenv(ENTANDO_SECURE_SECRET_COOKIES),
UrlUtils.determineForceHttps()));
}

/**
* Same rule StartupListener applies to the container session cookie.
*/
static boolean isSecureCookie(String secureFlag, boolean forceHttps) {
return StringUtils.isNotEmpty(secureFlag) ? Boolean.parseBoolean(secureFlag) : forceHttps;
}

/**
* Copies the servlet session cookie config as Spring Session's default serializer does, adding Secure when
* requested; otherwise Secure follows request.isSecure().
*/
DefaultCookieSerializer createCookieSerializer(boolean secure) {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
SessionCookieConfig config = null;
if (this.servletContext != null) {
try {
config = this.servletContext.getSessionCookieConfig();
} catch (UnsupportedOperationException ex) {
log.warn("Unable to obtain SessionCookieConfig: {}", ex.getMessage());
}
}
if (config != null) {
if (config.getName() != null) {
serializer.setCookieName(config.getName());
}
if (config.getDomain() != null) {
serializer.setDomainName(config.getDomain());
}
if (config.getPath() != null) {
serializer.setCookiePath(config.getPath());
}
if (config.getMaxAge() != -1) {
serializer.setCookieMaxAge(config.getMaxAge());
}
}
if (secure) {
serializer.setUseSecureCookie(true);
log.info("Spring Session cookie Secure flag: TRUE");
}
return serializer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import java.util.List;
import jakarta.servlet.Filter;
import java.util.List;
import org.entando.entando.TestEntandoJndiUtils;
import org.entando.entando.aps.system.services.cache.RedisEnvironmentVariables;
import org.entando.entando.plugins.jpredis.utils.RedisTestExtension;
Expand All @@ -14,8 +14,9 @@
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.session.data.redis.RedisSessionRepository;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
Expand Down Expand Up @@ -50,6 +51,9 @@ static void setUp(MockedStatic<RedisEnvironmentVariables> mockedEnv) {
@Autowired
private RedisClient redisClient;

@Autowired
private CookieSerializer cookieSerializer;

@Test
void testRedisSessionActive(GenericContainer redisContainer) throws Exception {
Assertions.assertTrue(springSessionRepositoryFilter instanceof SessionRepositoryFilter);
Expand All @@ -68,4 +72,11 @@ void testRedisSessionActive(GenericContainer redisContainer) throws Exception {
}
}
}

@Test
void testSessionFilterUsesCookieSerializerBean() {
Object resolver = ReflectionTestUtils.getField(springSessionRepositoryFilter, "httpSessionIdResolver");
Assertions.assertTrue(resolver instanceof CookieHttpSessionIdResolver);
Assertions.assertSame(cookieSerializer, ReflectionTestUtils.getField(resolver, "cookieSerializer"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.entando.entando.plugins.jpredis.aps.system.redis.session;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.web.http.CookieSerializer.CookieValue;
import org.springframework.session.web.http.DefaultCookieSerializer;

class RedisSessionConfigTest {

@Test
void testIsSecureCookie() {
Assertions.assertTrue(RedisSessionConfig.isSecureCookie("true", false));
Assertions.assertTrue(RedisSessionConfig.isSecureCookie("TRUE", false));
Assertions.assertFalse(RedisSessionConfig.isSecureCookie("false", true));
Assertions.assertTrue(RedisSessionConfig.isSecureCookie(null, true));
Assertions.assertTrue(RedisSessionConfig.isSecureCookie("", true));
Assertions.assertFalse(RedisSessionConfig.isSecureCookie(null, false));
Assertions.assertFalse(RedisSessionConfig.isSecureCookie("", false));
}

@Test
void testSecureCookieOverHttp() {
String header = writeCookie(new RedisSessionConfig().createCookieSerializer(true), false);
Assertions.assertTrue(header.startsWith("SESSION="));
}

@Test
void testNotSecureCookieFollowsRequest() {
RedisSessionConfig config = new RedisSessionConfig();
Assertions.assertFalse(writeCookie(config.createCookieSerializer(false), false).contains("Secure"));
Assertions.assertTrue(writeCookie(config.createCookieSerializer(false), true).contains("; Secure"));
}

@Test
void testSessionCookieConfigCopied() {
MockServletContext servletContext = new MockServletContext();
servletContext.getSessionCookieConfig().setName("CUSTOM");
servletContext.getSessionCookieConfig().setPath("/ctx");
servletContext.getSessionCookieConfig().setDomain("example.org");
servletContext.getSessionCookieConfig().setMaxAge(60);
RedisSessionConfig config = new RedisSessionConfig();
config.setServletContext(servletContext);

String header = writeCookie(config.createCookieSerializer(true), false);

Assertions.assertTrue(header.startsWith("CUSTOM="));
Assertions.assertTrue(header.contains("; Max-Age=60"));
Assertions.assertTrue(header.contains("; Domain=example.org"));
Assertions.assertTrue(header.contains("; Path=/ctx"));
Assertions.assertTrue(header.contains("; Secure"));
}

@Test
void testDefaultNameWithoutSessionCookieConfigName() {
RedisSessionConfig config = new RedisSessionConfig();
config.setServletContext(new MockServletContext());
Assertions.assertTrue(writeCookie(config.createCookieSerializer(false), false).startsWith("SESSION="));
}

private String writeCookie(DefaultCookieSerializer serializer, boolean secureRequest) {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSecure(secureRequest);
MockHttpServletResponse response = new MockHttpServletResponse();
serializer.writeCookieValue(new CookieValue(request, response, "id"));
return response.getHeader("Set-Cookie");
}
}
Loading