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..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,9 +47,10 @@ * @author Graeme Rocher * @since 1.0 */ -@Filter("/**") +@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. */ @@ -90,11 +93,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 +137,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 +148,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..38cd48bc --- /dev/null +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfiguration.java @@ -0,0 +1,32 @@ +/* + * 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.core.annotation.NonNull; +import io.micronaut.core.util.Toggleable; + +/** + * Configuration {@link HttpSessionFilter}. + * @author Sergio del Amo + * @since 4.6.0 + */ +public interface HttpSessionFilterConfiguration extends Toggleable { + /** + * @return Regular Expression Pattern the {@link HttpSessionFilter} should match. + */ + @NonNull + 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 new file mode 100644 index 00000000..519212bf --- /dev/null +++ b/session/src/main/java/io/micronaut/session/http/HttpSessionFilterConfigurationProperties.java @@ -0,0 +1,84 @@ +/* + * 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"; + + /** + * 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 getRegexPattern() { + return regexPattern; + } + + /** + * Pattern the {@link HttpSessionFilter} should match. Default value {@value #DEFAULT_REGEX_PATTERN}. + * + * @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 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 new file mode 100644 index 00000000..fe56f8a5 --- /dev/null +++ b/session/src/test/java/io/micronaut/session/HttpSessionFilterConfigurationTest.java @@ -0,0 +1,24 @@ +package io.micronaut.session; + +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.getRegexPattern(), + "Default exclude pattern should be '^.*$'"); + + 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