Upgrade to Thymeleaf and Security Extras 3.1.0-M1

Closes gh-49452
Closes gh-49453
pull/29178/head
Andy Wilkinson 3 years ago
parent 12cd97a20c
commit 26fecbe230

@ -178,9 +178,9 @@ dependencies {
exclude group: "jakarta.mail", module: "jakarta.mail-api"
}
optional("org.thymeleaf:thymeleaf")
optional("org.thymeleaf:thymeleaf-spring5")
optional("org.thymeleaf:thymeleaf-spring6")
optional("org.thymeleaf.extras:thymeleaf-extras-java8time")
optional("org.thymeleaf.extras:thymeleaf-extras-springsecurity5")
optional("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
optional("redis.clients:jedis")
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))

@ -0,0 +1,79 @@
/*
* Copyright 2012-2022 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
*
* 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 org.springframework.boot.autoconfigure.thymeleaf;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.spring6.ISpringTemplateEngine;
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.SpringWebFluxTemplateEngine;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration classes for Thymeleaf's {@link ITemplateEngine}. Imported by
* {@link ThymeleafAutoConfiguration}.
*
* @author Andy Wilkinson
*/
class TemplateEngineConfigurations {
@Configuration(proxyBeanMethods = false)
static class DefaultTemplateEngineConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ReactiveTemplateEngineConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
SpringWebFluxTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -18,26 +18,21 @@ package org.springframework.boot.autoconfigure.thymeleaf;
import java.util.LinkedHashMap;
import javax.servlet.DispatcherType;
import jakarta.servlet.DispatcherType;
import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.ISpringTemplateEngine;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@ -57,6 +52,7 @@ import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
@ -79,6 +75,8 @@ import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
@Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class,
TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class })
public class ThymeleafAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ -129,23 +127,6 @@ public class ThymeleafAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
protected static class ThymeleafDefaultConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
@ -198,25 +179,6 @@ public class ThymeleafAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
static class ThymeleafReactiveConfiguration {
@Bean
@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
SpringWebFluxTemplateEngine templateEngine(ThymeleafProperties properties,
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
return engine;
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.REACTIVE)
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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.
@ -34,7 +34,7 @@ public class ThymeleafTemplateAvailabilityProvider implements TemplateAvailabili
@Override
public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,
ResourceLoader resourceLoader) {
if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine", classLoader)) {
if (ClassUtils.isPresent("org.thymeleaf.spring6.SpringTemplateEngine", classLoader)) {
String prefix = environment.getProperty("spring.thymeleaf.prefix", ThymeleafProperties.DEFAULT_PREFIX);
String suffix = environment.getProperty("spring.thymeleaf.suffix", ThymeleafProperties.DEFAULT_SUFFIX);
return resourceLoader.getResource(prefix + view + suffix).exists();

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.thymeleaf;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Locale;
@ -26,13 +27,13 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.IContext;
import org.thymeleaf.extras.springsecurity5.util.SpringSecurityContextUtils;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.context.webflux.SpringWebFluxContext;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.extras.springsecurity6.util.SpringSecurityContextUtils;
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.spring6.web.webflux.SpringWebFluxWebApplication;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -198,7 +199,8 @@ class ThymeleafReactiveAutoConfigurationTests {
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
exchange.getAttributes().put(SpringSecurityContextUtils.SECURITY_CONTEXT_MODEL_ATTRIBUTE_NAME,
new SecurityContextImpl(new TestingAuthenticationToken("alice", "admin")));
IContext attrs = new SpringWebFluxContext(exchange);
WebContext attrs = new WebContext(SpringWebFluxWebApplication.buildApplication(null).buildExchange(exchange,
Locale.US, MediaType.TEXT_HTML, StandardCharsets.UTF_8));
String result = engine.process("security-dialect", attrs);
assertThat(result).isEqualTo("<html><body><div>alice</div></body></html>" + System.lineSeparator());
});

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -22,7 +22,8 @@ import java.util.EnumSet;
import java.util.Locale;
import java.util.Map;
import javax.servlet.DispatcherType;
import jakarta.servlet.DispatcherType;
import jakarta.servlet.ServletContext;
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
import nz.net.ultraq.thymeleaf.layoutdialect.decorators.strategies.GroupingStrategy;
@ -31,11 +32,12 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafView;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafView;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.web.servlet.JakartaServletWebApplication;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@ -81,7 +83,7 @@ class ThymeleafServletAutoConfigurationTests {
@Test
void autoConfigurationBackOffWithoutThymeleafSpring() {
this.contextRunner.withClassLoader(new FilteredClassLoader("org.thymeleaf.spring5"))
this.contextRunner.withClassLoader(new FilteredClassLoader("org.thymeleaf.spring6"))
.run((context) -> assertThat(context).doesNotHaveBean(TemplateEngine.class));
}
@ -183,7 +185,7 @@ class ThymeleafServletAutoConfigurationTests {
ThymeleafView view = (ThymeleafView) context.getBean(ThymeleafViewResolver.class).resolveViewName("view",
Locale.UK);
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletRequest request = new MockHttpServletRequest(context.getBean(ServletContext.class));
request.setAttribute(RequestContext.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
view.render(Collections.singletonMap("foo", "bar"), request, response);
String result = response.getContentAsString();
@ -217,8 +219,10 @@ class ThymeleafServletAutoConfigurationTests {
void useSecurityDialect() {
this.contextRunner.run((context) -> {
TemplateEngine engine = context.getBean(TemplateEngine.class);
WebContext attrs = new WebContext(new MockHttpServletRequest(), new MockHttpServletResponse(),
new MockServletContext());
MockServletContext servletContext = new MockServletContext();
JakartaServletWebApplication webApplication = JakartaServletWebApplication.buildApplication(servletContext);
WebContext attrs = new WebContext(webApplication.buildExchange(new MockHttpServletRequest(servletContext),
new MockHttpServletResponse()));
try {
SecurityContextHolder
.setContext(new SecurityContextImpl(new TestingAuthenticationToken("alice", "admin")));

@ -1,6 +1,6 @@
package app
@Grab("thymeleaf-spring5")
@Grab("thymeleaf-spring6")
@Controller
class Example {
@ -10,24 +10,3 @@ class Example {
return "home"
}
}
@Configuration(proxyBeanMethods = false)
@Log
class MvcConfiguration extends WebMvcConfigurerAdapter {
@Override
void addInterceptors(InterceptorRegistry registry) {
log.info "Registering interceptor"
registry.addInterceptor(interceptor())
}
@Bean
HandlerInterceptor interceptor() {
log.info "Creating interceptor"
[
postHandle: { request, response, handler, mav ->
log.info "Intercepted: model=" + mav.model
}
] as HandlerInterceptorAdapter
}
}

@ -17,7 +17,17 @@
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>thymeleaf-snapshot</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</profile>
</profiles>
</settings>
</settings>

@ -1436,11 +1436,11 @@ bom {
]
}
}
library("Thymeleaf", "3.0.12.RELEASE") {
library("Thymeleaf", "3.1.0.M1") {
group("org.thymeleaf") {
modules = [
"thymeleaf",
"thymeleaf-spring5"
"thymeleaf-spring6"
]
}
}
@ -1458,10 +1458,10 @@ bom {
]
}
}
library("Thymeleaf Extras SpringSecurity", "3.0.4.RELEASE") {
library("Thymeleaf Extras SpringSecurity", "3.1.0.M1") {
group("org.thymeleaf.extras") {
modules = [
"thymeleaf-extras-springsecurity5"
"thymeleaf-extras-springsecurity6"
]
}
}

@ -207,7 +207,6 @@ task aggregatedJavadoc(type: Javadoc) {
"https://docs.spring.io/spring-security/site/docs/${versionConstraints["org.springframework.security:spring-security-core"]}/api/",
"https://jakarta.ee/specifications/platform/9/apidocs/",
"https://tomcat.apache.org/tomcat-${tomcatDocsVersion}-doc/api/",
"https://www.thymeleaf.org/apidocs/thymeleaf/${versionConstraints["org.thymeleaf:thymeleaf"]}/"
] as String[]
}
}

@ -6,6 +6,6 @@ description = "Starter for building MVC web applications using Thymeleaf views"
dependencies {
api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter"))
api("org.thymeleaf:thymeleaf-spring5")
api("org.thymeleaf:thymeleaf-spring6")
api("org.thymeleaf.extras:thymeleaf-extras-java8time")
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -18,7 +18,7 @@ package smoketest.web.thymeleaf;
import java.util.Calendar;
import javax.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotEmpty;
public class Message {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2021 the original author or authors.
* Copyright 2012-2022 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.
@ -16,7 +16,7 @@
package smoketest.web.thymeleaf.mvc;
import javax.validation.Valid;
import jakarta.validation.Valid;
import smoketest.web.thymeleaf.Message;
import smoketest.web.thymeleaf.MessageRepository;

Loading…
Cancel
Save