From ef31ae1a98447ffa9edd3709833a38c53912fe02 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Thu, 5 Sep 2024 15:12:52 +0200 Subject: [PATCH] Render One Time Token UIs using lightweight templates --- ...neTimeTokenSubmitPageGeneratingFilter.java | 102 ++++++----- ...eTokenSubmitPageGeneratingFilterTests.java | 169 +++++++++++++++++- 2 files changed, 222 insertions(+), 49 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java index 8d47ebc7bd..86681958ae 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilter.java @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -33,7 +34,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.util.HtmlUtils; /** * Creates a default one-time token submit page. If the request contains a {@code token} @@ -65,54 +65,27 @@ public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePer private String generateHtml(HttpServletRequest request) { String token = request.getParameter("token"); - String inputValue = StringUtils.hasText(token) ? HtmlUtils.htmlEscape(token) : ""; - String input = ""; - return """ - - - - One-Time Token Login - - - - """ - + CssUtils.getCssStyleBlock().indent(4) - + """ - - - -
- """ - + "
" + """ -

Please input the token

-

- - """ + input + """ -

- - """ + renderHiddenInputs(request) + """ -
-
- - - """; + String tokenValue = StringUtils.hasText(token) ? token : ""; + + String hiddenInputs = this.resolveHiddenInputs.apply(request) + .entrySet() + .stream() + .map((inputKeyValue) -> renderHiddenInput(inputKeyValue.getKey(), inputKeyValue.getValue())) + .collect(Collectors.joining("\n")); + + return HtmlTemplates.fromTemplate(ONE_TIME_TOKEN_SUBMIT_PAGE_TEMPLATE) + .withRawHtml("cssStyle", CssUtils.getCssStyleBlock().indent(4)) + .withValue("tokenValue", tokenValue) + .withValue("loginProcessingUrl", this.loginProcessingUrl) + .withRawHtml("hiddenInputs", hiddenInputs) + .render(); } - private String renderHiddenInputs(HttpServletRequest request) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry input : this.resolveHiddenInputs.apply(request).entrySet()) { - sb.append("\n"); - } - return sb.toString(); + private String renderHiddenInput(String name, String value) { + return HtmlTemplates.fromTemplate(HIDDEN_HTML_INPUT_TEMPLATE) + .withValue("name", name) + .withValue("value", value) + .render(); } public void setResolveHiddenInputs(Function> resolveHiddenInputs) { @@ -135,4 +108,39 @@ public final class DefaultOneTimeTokenSubmitPageGeneratingFilter extends OncePer this.loginProcessingUrl = loginProcessingUrl; } + private static final String ONE_TIME_TOKEN_SUBMIT_PAGE_TEMPLATE = """ + + + + One-Time Token Login + + + + {{cssStyle}} + + + +
+ +
+ + + """; + + private static final String HIDDEN_HTML_INPUT_TEMPLATE = """ + + """; + } diff --git a/web/src/test/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java index df4eab303d..f92bfbedb4 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/ui/DefaultOneTimeTokenSubmitPageGeneratingFilterTests.java @@ -16,6 +16,8 @@ package org.springframework.security.web.authentication.ui; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -72,8 +74,7 @@ class DefaultOneTimeTokenSubmitPageGeneratingFilterTests { this.filter.setLoginProcessingUrl("/login/another"); this.filter.doFilterInternal(this.request, this.response, this.filterChain); String response = this.response.getContentAsString(); - assertThat(response).contains( - "
\t

Please input the token

"); + assertThat(response).contains(""); } @Test @@ -85,4 +86,168 @@ class DefaultOneTimeTokenSubmitPageGeneratingFilterTests { ""); } + @Test + void filterThenRenders() throws Exception { + this.request.setParameter("token", "this<>!@#\""); + this.filter.setLoginProcessingUrl("/login/another"); + this.filter.setResolveHiddenInputs((request) -> Map.of("_csrf", "csrf-token-value")); + this.filter.doFilterInternal(this.request, this.response, this.filterChain); + String response = this.response.getContentAsString(); + assertThat(response).isEqualTo( + """ + + + + One-Time Token Login + + + + + + + +
+ +

Please input the token

+

+ + +

+ + + +
+ + + """); + } + }