From d29d80e00a36055f37cc5c9cda2c92cc887ee15d Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 4 Nov 2024 15:33:05 +0100 Subject: [PATCH 01/10] make the HttpSessionFilter pattern configurable --- .../session/http/HttpSessionFilter.java | 14 +++-- .../http/HttpSessionFilterConfiguration.java | 58 +++++++++++++++++++ 2 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index c0c8f320..d22f3dca 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -34,7 +34,7 @@ import io.micronaut.session.annotation.SessionValue; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; - +import io.micronaut.http.filter.FilterPatternStyle; import java.util.List; import java.util.Optional; @@ -45,7 +45,8 @@ * @author Graeme Rocher * @since 1.0 */ -@Filter("/**") + +@Filter(patternStyle = FilterPatternStyle.REGEX, value = "${http.session.filter.regex-pattern:/.*}") public class HttpSessionFilter implements HttpServerFilter { /** @@ -57,7 +58,7 @@ public class HttpSessionFilter implements HttpServerFilter { * Constant for Micronaut SESSION attribute. */ public static final CharSequence SESSION_ATTRIBUTE = "micronaut.SESSION"; - + private final HttpSessionFilterConfiguration config; private final SessionStore sessionStore; private final HttpSessionIdResolver[] resolvers; private final HttpSessionIdEncoder[] encoders; @@ -68,11 +69,13 @@ public class HttpSessionFilter implements HttpServerFilter { * @param sessionStore The session store * @param resolvers The HTTP session id resolvers * @param encoders The HTTP session id encoders + * @param config the configuration for the HttpSessionFilter */ - public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders) { + public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders, HttpSessionFilterConfiguration config) { this.sessionStore = sessionStore; this.resolvers = resolvers; this.encoders = encoders; + this.config = config; } @Override @@ -82,6 +85,9 @@ public int getOrder() { @Override public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { + if (!request.getUri().getPath().matches(config.getRegexPattern())) { + return chain.proceed(request); + } request.setAttribute(HttpSessionFilter.class.getName(), true); try { for (HttpSessionIdResolver resolver : resolvers) { diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java new file mode 100644 index 00000000..dfd23e11 --- /dev/null +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017-2024 original 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 + * + * https://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 io.micronaut.session.http; + +import io.micronaut.context.annotation.ConfigurationProperties; +import io.micronaut.core.annotation.Internal; + +/** + * Configuration properties for the HttpSessionFilter. + * + * This class contains settings for the session filter, including + * a regex pattern to define which paths should have session handling applied. + */ +@Internal +@ConfigurationProperties("http.session.filter") +public class HttpSessionFilterConfiguration { + + /** + * The regex pattern for filtering paths that should have session handling. + */ + private String regexPattern = "/.*"; + + /** + * Gets the regex pattern for filtering paths that should have session handling. + * + * Subclasses may override this method to provide a different regex pattern. + * However, care should be taken to ensure that the new pattern remains compatible + * with the intended usage of session handling. + * + * @return The regex pattern for filtering paths. + */ + public String getRegexPattern() { + return regexPattern; + } + + /** + * Sets the regex pattern for filtering paths that should have session handling. + * + * @param regexPattern The regex pattern to apply for filtering paths. + * Default is "/.*", which matches all paths. + */ + public void setRegexPattern(String regexPattern) { + this.regexPattern = regexPattern; + } +} From abdd2c97a46fcb5bc62a56d197033004ad75417a Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Tue, 5 Nov 2024 10:13:31 +0100 Subject: [PATCH 02/10] Add tests --- .../http/HttpSessionFilterConfiguration.java | 3 +- .../HttpSessionFilterConfigurationTest.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index dfd23e11..dac6ecce 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -53,6 +53,7 @@ public String getRegexPattern() { * Default is "/.*", which matches all paths. */ public void setRegexPattern(String regexPattern) { - this.regexPattern = regexPattern; + this.regexPattern = (regexPattern != null) ? regexPattern : "/.*"; + } } diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java new file mode 100644 index 00000000..2a72f5b7 --- /dev/null +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -0,0 +1,35 @@ + +package io.micronaut.session; +import io.micronaut.session.http.HttpSessionFilterConfiguration; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class HttpSessionFilterConfigurationTest { + + @Test + void testRegexPatternDefault() { + HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); + assertEquals("/.*", config.getRegexPattern(), "Default regex pattern should match all paths."); + } + + @Test + void testSetRegexPattern() { + HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); + config.setRegexPattern("/assets/.*"); + assertEquals("/assets/.*", config.getRegexPattern(), "Regex pattern should be updated correctly."); + } + @Test + void testSetRegexPatternToNull() { + HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); + config.setRegexPattern(null); + assertEquals("/.*", config.getRegexPattern(), "If set to null, regex pattern should revert to the default."); + } + @Test + void testSetRegexPatternToEmptyString() { + HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); + config.setRegexPattern(""); + assertEquals("", config.getRegexPattern(), "Regex pattern should accept an empty string if explicitly set."); + } + +} From b65178d63550ff47d4de91c6ed07c4e0f75c2bdc Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 9 Dec 2024 12:49:51 +0100 Subject: [PATCH 03/10] add and fix the tests --- .../session/http/HttpSessionFilter.java | 33 ++++---- .../http/HttpSessionFilterConfiguration.java | 57 ++------------ .../HttpSessionFilterConfigurationTest.java | 76 ++++++++++++++----- .../MalformedSessionCookieValueTest.java | 47 ------------ 4 files changed, 80 insertions(+), 133 deletions(-) delete mode 100644 session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index d22f3dca..ceb11b7f 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -34,7 +34,7 @@ import io.micronaut.session.annotation.SessionValue; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; -import io.micronaut.http.filter.FilterPatternStyle; + import java.util.List; import java.util.Optional; @@ -45,10 +45,8 @@ * @author Graeme Rocher * @since 1.0 */ - -@Filter(patternStyle = FilterPatternStyle.REGEX, value = "${http.session.filter.regex-pattern:/.*}") +@Filter("${micronaut.session.filter.exclude-pattern:/**}") public class HttpSessionFilter implements HttpServerFilter { - /** * The order of the filter. */ @@ -58,10 +56,11 @@ public class HttpSessionFilter implements HttpServerFilter { * Constant for Micronaut SESSION attribute. */ public static final CharSequence SESSION_ATTRIBUTE = "micronaut.SESSION"; - private final HttpSessionFilterConfiguration config; + private final SessionStore sessionStore; private final HttpSessionIdResolver[] resolvers; private final HttpSessionIdEncoder[] encoders; + private final HttpSessionFilterConfiguration configuration; /** * Constructor. @@ -69,13 +68,12 @@ public class HttpSessionFilter implements HttpServerFilter { * @param sessionStore The session store * @param resolvers The HTTP session id resolvers * @param encoders The HTTP session id encoders - * @param config the configuration for the HttpSessionFilter */ - public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders, HttpSessionFilterConfiguration config) { + public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders, HttpSessionFilterConfiguration configuration) { this.sessionStore = sessionStore; this.resolvers = resolvers; this.encoders = encoders; - this.config = config; + this.configuration = configuration; } @Override @@ -85,9 +83,6 @@ public int getOrder() { @Override public Publisher> doFilter(HttpRequest request, ServerFilterChain chain) { - if (!request.getUri().getPath().matches(config.getRegexPattern())) { - return chain.proceed(request); - } request.setAttribute(HttpSessionFilter.class.getName(), true); try { for (HttpSessionIdResolver resolver : resolvers) { @@ -96,11 +91,11 @@ public Publisher> doFilter(HttpRequest request, Server String id = ids.get(0); Publisher> sessionLookup = Publishers.fromCompletableFuture(() -> sessionStore.findSession(id)); Flux> storeSessionInAttributes = Flux - .from(sessionLookup) - .switchMap(session -> { - session.ifPresent(entries -> request.getAttributes().put(SESSION_ATTRIBUTE, entries)); - return chain.proceed(request); - }); + .from(sessionLookup) + .switchMap(session -> { + session.ifPresent(entries -> request.getAttributes().put(SESSION_ATTRIBUTE, entries)); + return chain.proceed(request); + }); return encodeSessionId(request, storeSessionInAttributes); } } @@ -140,7 +135,7 @@ private Publisher> encodeSessionId(HttpRequest request if (opt.isPresent()) { Session session = opt.get(); if (sessionAttr != null) { - session.put(sessionAttr, body.get()); + session.put(sessionAttr, body.get()); } if (session.isNew() || session.isModified()) { @@ -151,8 +146,8 @@ private Publisher> encodeSessionId(HttpRequest request Session newSession = sessionStore.newSession(); newSession.put(sessionAttr, body.get()); return Flux - .from(Publishers.fromCompletableFuture(() -> sessionStore.save(newSession))) - .map(s -> new SessionAndResponse(Optional.of(s), response)); + .from(Publishers.fromCompletableFuture(() -> sessionStore.save(newSession))) + .map(s -> new SessionAndResponse(Optional.of(s), response)); } return Flux.just(new SessionAndResponse(opt, response)); }); diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index dac6ecce..63fa297c 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -1,59 +1,16 @@ -/* - * Copyright 2017-2024 original 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 - * - * https://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 io.micronaut.session.http; import io.micronaut.context.annotation.ConfigurationProperties; -import io.micronaut.core.annotation.Internal; -/** - * Configuration properties for the HttpSessionFilter. - * - * This class contains settings for the session filter, including - * a regex pattern to define which paths should have session handling applied. - */ -@Internal -@ConfigurationProperties("http.session.filter") -public class HttpSessionFilterConfiguration { +@ConfigurationProperties("micronaut.session.filter") +public class HttpSessionFilterConfiguration { + private String excludePattern = "/**"; - /** - * The regex pattern for filtering paths that should have session handling. - */ - private String regexPattern = "/.*"; - - /** - * Gets the regex pattern for filtering paths that should have session handling. - * - * Subclasses may override this method to provide a different regex pattern. - * However, care should be taken to ensure that the new pattern remains compatible - * with the intended usage of session handling. - * - * @return The regex pattern for filtering paths. - */ - public String getRegexPattern() { - return regexPattern; + public String getExcludePattern() { + return excludePattern; } - /** - * Sets the regex pattern for filtering paths that should have session handling. - * - * @param regexPattern The regex pattern to apply for filtering paths. - * Default is "/.*", which matches all paths. - */ - public void setRegexPattern(String regexPattern) { - this.regexPattern = (regexPattern != null) ? regexPattern : "/.*"; - + public void setExcludePattern(String excludePattern) { + this.excludePattern = excludePattern; } } diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java index 2a72f5b7..aade9203 100644 --- a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -1,35 +1,77 @@ - package io.micronaut.session; -import io.micronaut.session.http.HttpSessionFilterConfiguration; + +import io.micronaut.context.annotation.Property; +import io.micronaut.session.http.HttpSessionFilterConfiguration; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.*; +@MicronautTest class HttpSessionFilterConfigurationTest { + @Inject + HttpSessionFilterConfiguration configuration; + @Test - void testRegexPatternDefault() { - HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); - assertEquals("/.*", config.getRegexPattern(), "Default regex pattern should match all paths."); + void testDefaultConfiguration() { + assertEquals("/**", configuration.getExcludePattern(), + "Default exclude pattern should be '/**'"); } @Test - void testSetRegexPattern() { - HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); - config.setRegexPattern("/assets/.*"); - assertEquals("/assets/.*", config.getRegexPattern(), "Regex pattern should be updated correctly."); + @Property(name = "micronaut.session.filter.exclude-pattern", value = "/static/**,/public/**") + void testMultipleExclusionPatterns() { + assertEquals("/static/**,/public/**", configuration.getExcludePattern()); + assertTrue(matchesExcludePattern("/static/image.jpg"), "Static path should be excluded"); + assertTrue(matchesExcludePattern("/public/script.js"), "Public path should be excluded"); + assertFalse(matchesExcludePattern("/api/users"), "API path shouldn't be excluded"); + assertFalse(matchesExcludePattern("/login"), "Login path shouldn't be excluded"); } + @Test - void testSetRegexPatternToNull() { - HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); - config.setRegexPattern(null); - assertEquals("/.*", config.getRegexPattern(), "If set to null, regex pattern should revert to the default."); + @Property(name = "micronaut.session.filter.exclude-pattern", value = "/admin/**") + void testSingleExclusionPattern() { + assertEquals("/admin/**", configuration.getExcludePattern()); + + assertTrue(matchesExcludePattern("/admin/users"), "Admin path should be excluded"); + assertTrue(matchesExcludePattern("/admin/settings"), "Admin path should be excluded"); + assertFalse(matchesExcludePattern("/user/profile"), "User path shouldn't be excluded"); } + @Test - void testSetRegexPatternToEmptyString() { - HttpSessionFilterConfiguration config = new HttpSessionFilterConfiguration(); - config.setRegexPattern(""); - assertEquals("", config.getRegexPattern(), "Regex pattern should accept an empty string if explicitly set."); + @Property(name = "micronaut.session.filter.exclude-pattern", value = "") + void testEmptyExclusionPattern() { + assertEquals("", configuration.getExcludePattern()); + assertFalse(matchesExcludePattern("/any/path"), "No path should match empty pattern"); } + + private boolean matchesExcludePattern(String path) { + if (configuration.getExcludePattern() == null || configuration.getExcludePattern().isEmpty()) { + return false; + } + + String[] patterns = configuration.getExcludePattern().split(","); + for (String pattern : patterns) { + if (pathMatches(pattern.trim(), path)) { + return true; + } + } + return false; + } + + private boolean pathMatches(String pattern, String path) { + if (pattern.isEmpty()) { + return false; + } + + String regex = "^" + pattern + .replace(".", "\\.") + .replace("**", ".*") + .replace("*", "[^/]*") + "$"; + return path.matches(regex); + } } diff --git a/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java b/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java deleted file mode 100644 index 2947dce1..00000000 --- a/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.micronaut.session; - -import io.micronaut.context.annotation.Property; -import io.micronaut.context.annotation.Requires; -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.HttpStatus; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Status; -import io.micronaut.http.client.BlockingHttpClient; -import io.micronaut.http.client.HttpClient; -import io.micronaut.http.client.annotation.Client; -import io.micronaut.http.client.exceptions.HttpClientResponseException; -import io.micronaut.http.cookie.Cookie; -import io.micronaut.test.extensions.junit5.annotation.MicronautTest; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -@Property(name = "spec.name", value = "MalformedSessionCookieValueTest") -@MicronautTest -class MalformedSessionCookieValueTest { - - @Test - void testMalformedSessionCookieValue(@Client("/") HttpClient httpClient) { - BlockingHttpClient client = httpClient.toBlocking(); - HttpRequest request = HttpRequest.GET("/teapot"); - HttpResponse response = assertDoesNotThrow(() -> client.exchange(request)); - assertEquals(HttpStatus.ACCEPTED, response.status()); - - HttpRequest requestWithInvalidCookie = HttpRequest.GET("/teapot").cookie(Cookie.of("SESSION", "_invalid_")); - HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.exchange(requestWithInvalidCookie)); - assertEquals(HttpStatus.BAD_REQUEST, ex.getStatus()); - } - - @Requires(property = "spec.name", value = "MalformedSessionCookieValueTest") - @Controller("/teapot") - static class MalformedSessionCookieValueController { - - @Get - @Status(HttpStatus.ACCEPTED) - void index() { - // no-op - } - } -} From 95c62a349ba0fc2004d5fb744b8bbf4702c22c4c Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 9 Dec 2024 13:08:34 +0100 Subject: [PATCH 04/10] restore the MalformedSessionCookieValueTest class --- .../MalformedSessionCookieValueTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java diff --git a/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java b/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java new file mode 100644 index 00000000..2947dce1 --- /dev/null +++ b/session/src/test/java/io/micronaut/session/MalformedSessionCookieValueTest.java @@ -0,0 +1,47 @@ +package io.micronaut.session; + +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Status; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.cookie.Cookie; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "spec.name", value = "MalformedSessionCookieValueTest") +@MicronautTest +class MalformedSessionCookieValueTest { + + @Test + void testMalformedSessionCookieValue(@Client("/") HttpClient httpClient) { + BlockingHttpClient client = httpClient.toBlocking(); + HttpRequest request = HttpRequest.GET("/teapot"); + HttpResponse response = assertDoesNotThrow(() -> client.exchange(request)); + assertEquals(HttpStatus.ACCEPTED, response.status()); + + HttpRequest requestWithInvalidCookie = HttpRequest.GET("/teapot").cookie(Cookie.of("SESSION", "_invalid_")); + HttpClientResponseException ex = assertThrows(HttpClientResponseException.class, () -> client.exchange(requestWithInvalidCookie)); + assertEquals(HttpStatus.BAD_REQUEST, ex.getStatus()); + } + + @Requires(property = "spec.name", value = "MalformedSessionCookieValueTest") + @Controller("/teapot") + static class MalformedSessionCookieValueController { + + @Get + @Status(HttpStatus.ACCEPTED) + void index() { + // no-op + } + } +} From 590deeae07f4723058941c17911474b1b92574b2 Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 9 Dec 2024 13:31:48 +0100 Subject: [PATCH 05/10] add the License Header to HttpSessionFilterConfiguration class --- .../http/HttpSessionFilterConfiguration.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index 63fa297c..a269e50b 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -1,3 +1,18 @@ +/* + * Copyright 2017-2024 original 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 + * + * https://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 io.micronaut.session.http; import io.micronaut.context.annotation.ConfigurationProperties; From 2efe525a748732e1b961ea9454d1275829425ce5 Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 4 Nov 2024 15:33:05 +0100 Subject: [PATCH 06/10] make the HttpSessionFilter pattern configurable --- .../session/http/HttpSessionFilter.java | 23 +++--- .../http/HttpSessionFilterConfiguration.java | 31 ++++++++ .../HttpSessionFilterConfigurationTest.java | 77 +++++++++++++++++++ 3 files changed, 120 insertions(+), 11 deletions(-) create mode 100644 session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java create mode 100644 session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index c0c8f320..ceb11b7f 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -45,9 +45,8 @@ * @author Graeme Rocher * @since 1.0 */ -@Filter("/**") +@Filter("${micronaut.session.filter.exclude-pattern:/**}") public class HttpSessionFilter implements HttpServerFilter { - /** * The order of the filter. */ @@ -61,6 +60,7 @@ public class HttpSessionFilter implements HttpServerFilter { private final SessionStore sessionStore; private final HttpSessionIdResolver[] resolvers; private final HttpSessionIdEncoder[] encoders; + private final HttpSessionFilterConfiguration configuration; /** * Constructor. @@ -69,10 +69,11 @@ public class HttpSessionFilter implements HttpServerFilter { * @param resolvers The HTTP session id resolvers * @param encoders The HTTP session id encoders */ - public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders) { + public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders, HttpSessionFilterConfiguration configuration) { this.sessionStore = sessionStore; this.resolvers = resolvers; this.encoders = encoders; + this.configuration = configuration; } @Override @@ -90,11 +91,11 @@ public Publisher> doFilter(HttpRequest request, Server String id = ids.get(0); Publisher> sessionLookup = Publishers.fromCompletableFuture(() -> sessionStore.findSession(id)); Flux> storeSessionInAttributes = Flux - .from(sessionLookup) - .switchMap(session -> { - session.ifPresent(entries -> request.getAttributes().put(SESSION_ATTRIBUTE, entries)); - return chain.proceed(request); - }); + .from(sessionLookup) + .switchMap(session -> { + session.ifPresent(entries -> request.getAttributes().put(SESSION_ATTRIBUTE, entries)); + return chain.proceed(request); + }); return encodeSessionId(request, storeSessionInAttributes); } } @@ -134,7 +135,7 @@ private Publisher> encodeSessionId(HttpRequest request if (opt.isPresent()) { Session session = opt.get(); if (sessionAttr != null) { - session.put(sessionAttr, body.get()); + session.put(sessionAttr, body.get()); } if (session.isNew() || session.isModified()) { @@ -145,8 +146,8 @@ private Publisher> encodeSessionId(HttpRequest request Session newSession = sessionStore.newSession(); newSession.put(sessionAttr, body.get()); return Flux - .from(Publishers.fromCompletableFuture(() -> sessionStore.save(newSession))) - .map(s -> new SessionAndResponse(Optional.of(s), response)); + .from(Publishers.fromCompletableFuture(() -> sessionStore.save(newSession))) + .map(s -> new SessionAndResponse(Optional.of(s), response)); } return Flux.just(new SessionAndResponse(opt, response)); }); diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java new file mode 100644 index 00000000..a269e50b --- /dev/null +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2024 original 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 + * + * https://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 io.micronaut.session.http; + +import io.micronaut.context.annotation.ConfigurationProperties; + +@ConfigurationProperties("micronaut.session.filter") +public class HttpSessionFilterConfiguration { + private String excludePattern = "/**"; + + public String getExcludePattern() { + return excludePattern; + } + + public void setExcludePattern(String excludePattern) { + this.excludePattern = excludePattern; + } +} diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java new file mode 100644 index 00000000..aade9203 --- /dev/null +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -0,0 +1,77 @@ +package io.micronaut.session; + + +import io.micronaut.context.annotation.Property; +import io.micronaut.session.http.HttpSessionFilterConfiguration; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@MicronautTest +class HttpSessionFilterConfigurationTest { + + @Inject + HttpSessionFilterConfiguration configuration; + + @Test + void testDefaultConfiguration() { + assertEquals("/**", configuration.getExcludePattern(), + "Default exclude pattern should be '/**'"); + } + + @Test + @Property(name = "micronaut.session.filter.exclude-pattern", value = "/static/**,/public/**") + void testMultipleExclusionPatterns() { + assertEquals("/static/**,/public/**", configuration.getExcludePattern()); + assertTrue(matchesExcludePattern("/static/image.jpg"), "Static path should be excluded"); + assertTrue(matchesExcludePattern("/public/script.js"), "Public path should be excluded"); + assertFalse(matchesExcludePattern("/api/users"), "API path shouldn't be excluded"); + assertFalse(matchesExcludePattern("/login"), "Login path shouldn't be excluded"); + } + + @Test + @Property(name = "micronaut.session.filter.exclude-pattern", value = "/admin/**") + void testSingleExclusionPattern() { + assertEquals("/admin/**", configuration.getExcludePattern()); + + assertTrue(matchesExcludePattern("/admin/users"), "Admin path should be excluded"); + assertTrue(matchesExcludePattern("/admin/settings"), "Admin path should be excluded"); + assertFalse(matchesExcludePattern("/user/profile"), "User path shouldn't be excluded"); + } + + @Test + @Property(name = "micronaut.session.filter.exclude-pattern", value = "") + void testEmptyExclusionPattern() { + assertEquals("", configuration.getExcludePattern()); + assertFalse(matchesExcludePattern("/any/path"), "No path should match empty pattern"); + } + + + private boolean matchesExcludePattern(String path) { + if (configuration.getExcludePattern() == null || configuration.getExcludePattern().isEmpty()) { + return false; + } + + String[] patterns = configuration.getExcludePattern().split(","); + for (String pattern : patterns) { + if (pathMatches(pattern.trim(), path)) { + return true; + } + } + return false; + } + + private boolean pathMatches(String pattern, String path) { + if (pattern.isEmpty()) { + return false; + } + + String regex = "^" + pattern + .replace(".", "\\.") + .replace("**", ".*") + .replace("*", "[^/]*") + "$"; + return path.matches(regex); + } +} From 99aa44a03a9fd04d57beebd07bc22ce63e00e5f3 Mon Sep 17 00:00:00 2001 From: Chaimaa Rouai Date: Mon, 9 Dec 2024 13:48:09 +0100 Subject: [PATCH 07/10] fix Checkstyle violations --- .../http/HttpSessionFilterConfiguration.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index a269e50b..5346e1a2 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -17,14 +17,41 @@ import io.micronaut.context.annotation.ConfigurationProperties; +/** + * Configuration class for the HTTP session filter. + *

+ * This class allows configuration of the pattern for excluding certain paths + * from session filtering in Micronaut applications. + *

+ */ @ConfigurationProperties("micronaut.session.filter") public class HttpSessionFilterConfiguration { private String excludePattern = "/**"; + /** + * Returns the exclude pattern used to filter certain paths from session handling. + *

+ * Subclasses can override this method to provide a different exclude pattern if needed. + * Be sure that the pattern follows the expected syntax and does not conflict with + * other filters or session configurations. + *

+ * + * @return the exclude pattern + */ public String getExcludePattern() { return excludePattern; } + /** + * Sets the exclude pattern used to filter certain paths from session handling. + *

+ * This method can be overridden by subclasses to customize the exclude pattern. + * When overriding this method, ensure that the pattern is compatible with other + * session filtering configurations, and that it does not conflict with other filters. + *

+ * + * @param excludePattern the exclude pattern to set + */ public void setExcludePattern(String excludePattern) { this.excludePattern = excludePattern; } From 400727613f98b367906485da8387a4c4c7b2b65e Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 11 Dec 2024 10:41:22 +0100 Subject: [PATCH 08/10] remove unused HttpSessionFilterConfiguration fix constructor --- .../java/io/micronaut/session/http/HttpSessionFilter.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index ceb11b7f..813f8abd 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -60,7 +60,6 @@ public class HttpSessionFilter implements HttpServerFilter { private final SessionStore sessionStore; private final HttpSessionIdResolver[] resolvers; private final HttpSessionIdEncoder[] encoders; - private final HttpSessionFilterConfiguration configuration; /** * Constructor. @@ -69,11 +68,10 @@ public class HttpSessionFilter implements HttpServerFilter { * @param resolvers The HTTP session id resolvers * @param encoders The HTTP session id encoders */ - public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders, HttpSessionFilterConfiguration configuration) { + public HttpSessionFilter(SessionStore sessionStore, HttpSessionIdResolver[] resolvers, HttpSessionIdEncoder[] encoders) { this.sessionStore = sessionStore; this.resolvers = resolvers; this.encoders = encoders; - this.configuration = configuration; } @Override From 2f16a10ab28a9af366dd35df7d734118b4a75661 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 11 Dec 2024 10:50:03 +0100 Subject: [PATCH 09/10] rename to path --- .../session/http/HttpSessionFilter.java | 2 +- .../http/HttpSessionFilterConfiguration.java | 43 +++------------ ...pSessionFilterConfigurationProperties.java | 53 +++++++++++++++++++ .../HttpSessionFilterConfigurationTest.java | 12 ++--- 4 files changed, 68 insertions(+), 42 deletions(-) create mode 100644 session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index 813f8abd..ac3ce7f2 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -45,7 +45,7 @@ * @author Graeme Rocher * @since 1.0 */ -@Filter("${micronaut.session.filter.exclude-pattern:/**}") +@Filter("${" + HttpSessionFilterConfigurationProperties.PATH_PROPERTY + ":" + HttpSessionFilterConfigurationProperties.DEFAULT_PATH + "}") public class HttpSessionFilter implements HttpServerFilter { /** * The order of the filter. diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index 5346e1a2..117987fb 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -15,44 +15,17 @@ */ package io.micronaut.session.http; -import io.micronaut.context.annotation.ConfigurationProperties; +import io.micronaut.core.annotation.NonNull; /** - * Configuration class for the HTTP session filter. - *

- * This class allows configuration of the pattern for excluding certain paths - * from session filtering in Micronaut applications. - *

+ * Configuration {@link HttpSessionFilter}. + * @author Sergio del Amo + * @since 4.6.0 */ -@ConfigurationProperties("micronaut.session.filter") -public class HttpSessionFilterConfiguration { - private String excludePattern = "/**"; - - /** - * Returns the exclude pattern used to filter certain paths from session handling. - *

- * Subclasses can override this method to provide a different exclude pattern if needed. - * Be sure that the pattern follows the expected syntax and does not conflict with - * other filters or session configurations. - *

- * - * @return the exclude pattern - */ - public String getExcludePattern() { - return excludePattern; - } - +public interface HttpSessionFilterConfiguration { /** - * Sets the exclude pattern used to filter certain paths from session handling. - *

- * This method can be overridden by subclasses to customize the exclude pattern. - * When overriding this method, ensure that the pattern is compatible with other - * session filtering configurations, and that it does not conflict with other filters. - *

- * - * @param excludePattern the exclude pattern to set + * @return Pattern the {@link HttpSessionFilter} should match. */ - public void setExcludePattern(String excludePattern) { - this.excludePattern = excludePattern; - } + @NonNull + String getPath(); } diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java new file mode 100644 index 00000000..9ea11fbc --- /dev/null +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java @@ -0,0 +1,53 @@ +/* + * Copyright 2017-2024 original 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 + * + * https://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 io.micronaut.session.http; + +import io.micronaut.context.annotation.ConfigurationProperties; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.session.SessionSettings; + +/** + * @author Sergio del Amo + * @since 4.6.0 + */ +@ConfigurationProperties(HttpSessionFilterConfigurationProperties.PREFIX) +@Internal +class HttpSessionFilterConfigurationProperties implements HttpSessionFilterConfiguration { + /** + * {@link HttpSessionFilterConfigurationProperties} prefix. + */ + @SuppressWarnings("WeakerAccess") + public static final String PREFIX = SessionSettings.PREFIX + ".filter"; + public static final String PATH_PROPERTY = PREFIX + ".path"; + public static final String DEFAULT_PATH = "/**"; + private String path = DEFAULT_PATH; + + @Override + @NonNull + public String getPath() { + return path; + } + + /** + * Pattern the {@link HttpSessionFilter} should match. Default value {@value #DEFAULT_PATH}. + * + * @param path the exclude pattern to set + */ + public void setPath(String path) { + this.path = path; + } +} diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java index aade9203..c63849e1 100644 --- a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -17,14 +17,14 @@ class HttpSessionFilterConfigurationTest { @Test void testDefaultConfiguration() { - assertEquals("/**", configuration.getExcludePattern(), + assertEquals("/**", configuration.getPath(), "Default exclude pattern should be '/**'"); } @Test @Property(name = "micronaut.session.filter.exclude-pattern", value = "/static/**,/public/**") void testMultipleExclusionPatterns() { - assertEquals("/static/**,/public/**", configuration.getExcludePattern()); + assertEquals("/static/**,/public/**", configuration.getPath()); assertTrue(matchesExcludePattern("/static/image.jpg"), "Static path should be excluded"); assertTrue(matchesExcludePattern("/public/script.js"), "Public path should be excluded"); assertFalse(matchesExcludePattern("/api/users"), "API path shouldn't be excluded"); @@ -34,7 +34,7 @@ void testMultipleExclusionPatterns() { @Test @Property(name = "micronaut.session.filter.exclude-pattern", value = "/admin/**") void testSingleExclusionPattern() { - assertEquals("/admin/**", configuration.getExcludePattern()); + assertEquals("/admin/**", configuration.getPath()); assertTrue(matchesExcludePattern("/admin/users"), "Admin path should be excluded"); assertTrue(matchesExcludePattern("/admin/settings"), "Admin path should be excluded"); @@ -44,17 +44,17 @@ void testSingleExclusionPattern() { @Test @Property(name = "micronaut.session.filter.exclude-pattern", value = "") void testEmptyExclusionPattern() { - assertEquals("", configuration.getExcludePattern()); + assertEquals("", configuration.getPath()); assertFalse(matchesExcludePattern("/any/path"), "No path should match empty pattern"); } private boolean matchesExcludePattern(String path) { - if (configuration.getExcludePattern() == null || configuration.getExcludePattern().isEmpty()) { + if (configuration.getPath() == null || configuration.getPath().isEmpty()) { return false; } - String[] patterns = configuration.getExcludePattern().split(","); + String[] patterns = configuration.getPath().split(","); for (String pattern : patterns) { if (pathMatches(pattern.trim(), path)) { return true; From 6cf6a787e22109a41882f6f2bf8dbc02af1f85f0 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 11 Dec 2024 11:26:34 +0100 Subject: [PATCH 10/10] implementation --- .../session/http/HttpSessionFilter.java | 6 +- .../http/HttpSessionFilterConfiguration.java | 7 ++- ...pSessionFilterConfigurationProperties.java | 49 ++++++++++++--- .../HttpSessionFilterConfigurationTest.java | 61 ++----------------- .../HttpSessionFilterDisabledTest.java | 23 +++++++ .../session/HttpSessionFilterPatternTest.java | 55 +++++++++++++++++ .../docs/guide/sessions/sessionFilter.adoc | 6 ++ src/main/docs/guide/toc.yml | 1 + 8 files changed, 138 insertions(+), 70 deletions(-) create mode 100644 session/src/test/java/io/micronaut/session/HttpSessionFilterDisabledTest.java create mode 100644 session/src/test/java/io/micronaut/session/HttpSessionFilterPatternTest.java create mode 100644 src/main/docs/guide/sessions/sessionFilter.adoc diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java index ac3ce7f2..c2e426a9 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilter.java @@ -15,6 +15,7 @@ */ package io.micronaut.session.http; +import io.micronaut.context.annotation.Requires; import io.micronaut.core.async.publisher.Publishers; import io.micronaut.core.util.CollectionUtils; import io.micronaut.core.util.StringUtils; @@ -24,6 +25,7 @@ import io.micronaut.http.MutableHttpResponse; import io.micronaut.http.annotation.Filter; import io.micronaut.http.exceptions.HttpStatusException; +import io.micronaut.http.filter.FilterPatternStyle; import io.micronaut.http.filter.HttpServerFilter; import io.micronaut.http.filter.ServerFilterChain; import io.micronaut.http.filter.ServerFilterPhase; @@ -45,7 +47,9 @@ * @author Graeme Rocher * @since 1.0 */ -@Filter("${" + HttpSessionFilterConfigurationProperties.PATH_PROPERTY + ":" + HttpSessionFilterConfigurationProperties.DEFAULT_PATH + "}") +@Requires(property = HttpSessionFilterConfigurationProperties.PROPERTY_ENABLED, notEquals = StringUtils.FALSE, defaultValue = StringUtils.TRUE) +@Filter(patternStyle = FilterPatternStyle.REGEX, + value = "${" + HttpSessionFilterConfigurationProperties.PROPERTY_REGEX_PATTERN + ":" + HttpSessionFilterConfigurationProperties.DEFAULT_REGEX_PATTERN + "}") public class HttpSessionFilter implements HttpServerFilter { /** * The order of the filter. diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java index 117987fb..38cd48bc 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -16,16 +16,17 @@ package io.micronaut.session.http; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.util.Toggleable; /** * Configuration {@link HttpSessionFilter}. * @author Sergio del Amo * @since 4.6.0 */ -public interface HttpSessionFilterConfiguration { +public interface HttpSessionFilterConfiguration extends Toggleable { /** - * @return Pattern the {@link HttpSessionFilter} should match. + * @return Regular Expression Pattern the {@link HttpSessionFilter} should match. */ @NonNull - String getPath(); + String getRegexPattern(); } diff --git a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java index 9ea11fbc..519212bf 100644 --- a/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java @@ -32,22 +32,53 @@ class HttpSessionFilterConfigurationProperties implements HttpSessionFilterConfi */ @SuppressWarnings("WeakerAccess") public static final String PREFIX = SessionSettings.PREFIX + ".filter"; - public static final String PATH_PROPERTY = PREFIX + ".path"; - public static final String DEFAULT_PATH = "/**"; - private String path = DEFAULT_PATH; + + /** + * The default enable value. + */ + @SuppressWarnings("WeakerAccess") + public static final boolean DEFAULT_ENABLED = true; + public static final String PROPERTY_ENABLED = PREFIX + ".enabled"; + public static final String PROPERTY_REGEX_PATTERN = PREFIX + ".regex-pattern"; + + /** + * The default regex pattern. + */ + @SuppressWarnings("WeakerAccess") + public static final String DEFAULT_REGEX_PATTERN = "^.*$"; + + private String regexPattern = DEFAULT_REGEX_PATTERN; + private boolean enabled = DEFAULT_ENABLED; @Override @NonNull - public String getPath() { - return path; + public String getRegexPattern() { + return regexPattern; } /** - * Pattern the {@link HttpSessionFilter} should match. Default value {@value #DEFAULT_PATH}. + * Pattern the {@link HttpSessionFilter} should match. Default value {@value #DEFAULT_REGEX_PATTERN}. * - * @param path the exclude pattern to set + * @param regexPattern the exclude pattern to set + */ + public void setRegexPattern(String regexPattern) { + this.regexPattern = regexPattern; + } + + /** + * Whether the {@link HttpSessionFilter} is enabled. Default value {@value #DEFAULT_ENABLED} + * @return true if you want to enable the {@link HttpSessionFilter} + */ + @Override + public boolean isEnabled() { + return this.enabled; + } + + /** + * Enables {@link HttpSessionFilter}. Default value {@value #DEFAULT_ENABLED} + * @param enabled True if it is enabled */ - public void setPath(String path) { - this.path = path; + public void setEnabled(boolean enabled) { + this.enabled = enabled; } } diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java index c63849e1..fe56f8a5 100644 --- a/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -1,7 +1,5 @@ package io.micronaut.session; - -import io.micronaut.context.annotation.Property; import io.micronaut.session.http.HttpSessionFilterConfiguration; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; @@ -17,61 +15,10 @@ class HttpSessionFilterConfigurationTest { @Test void testDefaultConfiguration() { - assertEquals("/**", configuration.getPath(), - "Default exclude pattern should be '/**'"); - } - - @Test - @Property(name = "micronaut.session.filter.exclude-pattern", value = "/static/**,/public/**") - void testMultipleExclusionPatterns() { - assertEquals("/static/**,/public/**", configuration.getPath()); - assertTrue(matchesExcludePattern("/static/image.jpg"), "Static path should be excluded"); - assertTrue(matchesExcludePattern("/public/script.js"), "Public path should be excluded"); - assertFalse(matchesExcludePattern("/api/users"), "API path shouldn't be excluded"); - assertFalse(matchesExcludePattern("/login"), "Login path shouldn't be excluded"); - } - - @Test - @Property(name = "micronaut.session.filter.exclude-pattern", value = "/admin/**") - void testSingleExclusionPattern() { - assertEquals("/admin/**", configuration.getPath()); - - assertTrue(matchesExcludePattern("/admin/users"), "Admin path should be excluded"); - assertTrue(matchesExcludePattern("/admin/settings"), "Admin path should be excluded"); - assertFalse(matchesExcludePattern("/user/profile"), "User path shouldn't be excluded"); - } - - @Test - @Property(name = "micronaut.session.filter.exclude-pattern", value = "") - void testEmptyExclusionPattern() { - assertEquals("", configuration.getPath()); - assertFalse(matchesExcludePattern("/any/path"), "No path should match empty pattern"); - } - - - private boolean matchesExcludePattern(String path) { - if (configuration.getPath() == null || configuration.getPath().isEmpty()) { - return false; - } - - String[] patterns = configuration.getPath().split(","); - for (String pattern : patterns) { - if (pathMatches(pattern.trim(), path)) { - return true; - } - } - return false; - } - - private boolean pathMatches(String pattern, String path) { - if (pattern.isEmpty()) { - return false; - } + assertEquals("^.*$", configuration.getRegexPattern(), + "Default exclude pattern should be '^.*$'"); - String regex = "^" + pattern - .replace(".", "\\.") - .replace("**", ".*") - .replace("*", "[^/]*") + "$"; - return path.matches(regex); + assertTrue( configuration.isEnabled(), + "filter is enabled by default"); } } diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterDisabledTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterDisabledTest.java new file mode 100644 index 00000000..31875384 --- /dev/null +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterDisabledTest.java @@ -0,0 +1,23 @@ +package io.micronaut.session; + +import io.micronaut.context.BeanContext; +import io.micronaut.context.annotation.Property; +import io.micronaut.core.util.StringUtils; +import io.micronaut.session.http.HttpSessionFilter; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "micronaut.session.filter.enabled", value = StringUtils.FALSE) +@MicronautTest(startApplication = false) +class HttpSessionFilterDisabledTest { + @Inject + BeanContext beanContext; + + @Test + void filterCanBeDisabled() { + assertFalse(beanContext.containsBean(HttpSessionFilter.class)); + } +} \ No newline at end of file diff --git a/session/src/test/java/io/micronaut/session/HttpSessionFilterPatternTest.java b/session/src/test/java/io/micronaut/session/HttpSessionFilterPatternTest.java new file mode 100644 index 00000000..c834a0ce --- /dev/null +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterPatternTest.java @@ -0,0 +1,55 @@ +package io.micronaut.session; + +import io.micronaut.context.annotation.Property; +import io.micronaut.context.annotation.Requires; +import io.micronaut.core.type.Argument; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.session.http.HttpSessionFilter; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Property(name = "micronaut.session.filter.regex-pattern", value = "^(?!/assets).*$") +@Property(name = "spec.name", value = "HttpSessionFilterPatternTest") +@MicronautTest +class HttpSessionFilterPatternTest { + + @Test + void testHttpSessionFilterPatternCanBeConfigured(@Client("/") HttpClient httpClient) { + BlockingHttpClient client = httpClient.toBlocking(); + Argument> arg = Argument.mapOf(String.class, Boolean.class); + HttpResponse> response = assertDoesNotThrow(() -> client.exchange(HttpRequest.GET("/assets"), arg)); + assertNotNull(response.body().get("hitSessionFilter")); + assertFalse(response.body().get("hitSessionFilter")); + response = assertDoesNotThrow(() -> client.exchange(HttpRequest.GET("/foobar"), arg)); + assertNotNull(response.body().get("hitSessionFilter")); + assertTrue(response.body().get("hitSessionFilter")); + } + + @Requires(property = "spec.name", value = "HttpSessionFilterPatternTest") + @Controller("/assets") + static class AssetsController { + @Get + Map index(HttpRequest request) { + return Map.of("hitSessionFilter", request.getAttribute(HttpSessionFilter.class.getName(), Boolean.class).orElse(false)); + } + } + + @Requires(property = "spec.name", value = "HttpSessionFilterPatternTest") + @Controller("/foobar") + static class FoobarController { + @Get + Map index(HttpRequest request) { + return Map.of("hitSessionFilter", request.getAttribute(HttpSessionFilter.class.getName(), Boolean.class).orElse(false)); + } + } +} diff --git a/src/main/docs/guide/sessions/sessionFilter.adoc b/src/main/docs/guide/sessions/sessionFilter.adoc new file mode 100644 index 00000000..37042eb3 --- /dev/null +++ b/src/main/docs/guide/sessions/sessionFilter.adoc @@ -0,0 +1,6 @@ +The core of Micronaut Session implementation is `io.micronaut.session.http.HttpSessionFilter`. + +The following configuration options are available for the SessionFilter Filter: + +include::{includedir}configurationProperties/io.micronaut.session.http.HttpSessionFilterConfigurationProperties.adoc[] + diff --git a/src/main/docs/guide/toc.yml b/src/main/docs/guide/toc.yml index 5682c788..cdea7049 100644 --- a/src/main/docs/guide/toc.yml +++ b/src/main/docs/guide/toc.yml @@ -2,6 +2,7 @@ introduction: title: Introduction sessions: title: Http Sessions + sessionFilter: Session Filter enablingSessions: Enabling Sessions redisSessions: Redis Sessions configuringSessionResolution: Configuring Session Resolution