From 4a380b84019240fabb83b299c60e33aeacda70e0 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 27 Dec 2016 17:42:01 -0500 Subject: [PATCH] Add RequestDataValueProcessor in spring-web-reactive Issue: SPR-15001 --- .../reactive/result/view/AbstractView.java | 20 ++++- .../reactive/result/view/RedirectView.java | 11 ++- .../reactive/result/view/RequestContext.java | 26 +++++-- .../view/RequestDataValueProcessor.java | 74 +++++++++++++++++++ 4 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestDataValueProcessor.java diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java index a3bfeb7dedb..8605074db6c 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java @@ -41,6 +41,10 @@ import org.springframework.web.server.ServerWebExchange; */ public abstract class AbstractView implements View, ApplicationContextAware { + /** Well-known name for the RequestDataValueProcessor in the bean factory. */ + public static final String REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME = "requestDataValueProcessor"; + + /** Logger that is available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -179,7 +183,21 @@ public abstract class AbstractView implements View, ApplicationContextAware { * @see #setRequestContextAttribute */ protected RequestContext createRequestContext(ServerWebExchange exchange, Map model) { - return new RequestContext(exchange, model, this.applicationContext); + return new RequestContext(exchange, model, getApplicationContext(), getRequestDataValueProcessor()); + } + + /** + * Return the {@link RequestDataValueProcessor} to use. + *

The default implementation looks in the {@link #getApplicationContext() + * Spring configuration} for a {@code RequestDataValueProcessor} bean with + * the name {@link #REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME}. + */ + protected RequestDataValueProcessor getRequestDataValueProcessor() { + if (getApplicationContext() != null) { + String beanName = REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME; + return getApplicationContext().getBean(beanName, RequestDataValueProcessor.class); + } + return null; } /** diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java index cbc22088c9a..2882a4f6b76 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java @@ -186,8 +186,10 @@ public class RedirectView extends AbstractUrlBasedView { } /** - * Create the target URL if necessary pre-pending the contextPath, expanding - * URI template variables, and appending the current request query. + * Create the target URL and, if necessary, pre-pend the contextPath, expand + * URI template variables, append the current request query, and apply the + * configured {@link #getRequestDataValueProcessor() + * RequestDataValueProcessor}. */ protected final String createTargetUrl(Map model, ServerWebExchange exchange) { @@ -206,7 +208,10 @@ public class RedirectView extends AbstractUrlBasedView { targetUrl = appendCurrentRequestQuery(targetUrl.toString(), exchange.getRequest()); } - return targetUrl.toString(); + String result = targetUrl.toString(); + + RequestDataValueProcessor processor = getRequestDataValueProcessor(); + return (processor != null ? processor.processUrl(exchange, result) : result); } @SuppressWarnings("unchecked") diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestContext.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestContext.java index 79e54f543bb..94ebace5b38 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestContext.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestContext.java @@ -57,17 +57,23 @@ public class RequestContext { private final MessageSource messageSource; + private Locale locale; + + private TimeZone timeZone; + private Boolean defaultHtmlEscape; private Map errorsMap; - private Locale locale; + private RequestDataValueProcessor dataValueProcessor; - private TimeZone timeZone; + public RequestContext(ServerWebExchange exchange, Map model, MessageSource messageSource) { + this(exchange, model, messageSource, null); + } - public RequestContext(ServerWebExchange exchange, Map model, - MessageSource messageSource) { + public RequestContext(ServerWebExchange exchange, Map model, MessageSource messageSource, + RequestDataValueProcessor dataValueProcessor) { Assert.notNull(exchange, "'exchange' is required"); Assert.notNull(model, "'model' is required"); @@ -75,11 +81,13 @@ public class RequestContext { this.exchange = exchange; this.model = model; this.messageSource = messageSource; - this.defaultHtmlEscape = null; // TODO Locale acceptLocale = exchange.getRequest().getHeaders().getAcceptLanguageAsLocale(); this.locale = acceptLocale != null ? acceptLocale : Locale.getDefault(); this.timeZone = TimeZone.getDefault(); // TODO + + this.defaultHtmlEscape = null; // TODO + this.dataValueProcessor = dataValueProcessor; } @@ -158,6 +166,14 @@ public class RequestContext { return this.defaultHtmlEscape; } + /** + * Return the {@link RequestDataValueProcessor} instance to apply to in form + * tag libraries and to redirect URLs. + */ + public Optional getRequestDataValueProcessor() { + return Optional.ofNullable(this.dataValueProcessor); + } + /** * Return the context path of the the current web application. This is * useful for building links to other resources within the application. diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestDataValueProcessor.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestDataValueProcessor.java new file mode 100644 index 00000000000..400db5047d7 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RequestDataValueProcessor.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2016 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 + * + * http://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.web.reactive.result.view; + +import java.util.Map; + +import org.springframework.web.server.ServerWebExchange; + +/** + * A contract for inspecting and potentially modifying request data values such + * as URL query parameters or form field values before they are rendered by a + * view or before a redirect. + * + *

Implementations may use this contract for example as part of a solution + * to provide data integrity, confidentiality, protection against cross-site + * request forgery (CSRF), and others or for other tasks such as automatically + * adding a hidden field to all forms and URLs. + * + *

View technologies that support this contract can obtain an instance to + * delegate to via {@link RequestContext#getRequestDataValueProcessor()}. + * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public interface RequestDataValueProcessor { + + /** + * Invoked when a new form action is rendered. + * @param exchange the current exchange + * @param action the form action + * @param httpMethod the form HTTP method + * @return the action to use, possibly modified + */ + String processAction(ServerWebExchange exchange, String action, String httpMethod); + + /** + * Invoked when a form field value is rendered. + * @param exchange the current exchange + * @param name the form field name + * @param value the form field value + * @param type the form field type ("text", "hidden", etc.) + * @return the form field value to use, possibly modified + */ + String processFormFieldValue(ServerWebExchange exchange, String name, String value, String type); + + /** + * Invoked after all form fields have been rendered. + * @param exchange the current exchange + * @return additional hidden form fields to be added, or {@code null} + */ + Map getExtraHiddenFields(ServerWebExchange exchange); + + /** + * Invoked when a URL is about to be rendered or redirected to. + * @param exchange the current exchange + * @param url the URL value + * @return the URL to use, possibly modified + */ + String processUrl(ServerWebExchange exchange, String url); + +}