Merge branch '5.3.x'
This commit is contained in:
commit
1574fed90b
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -23,8 +23,9 @@ import org.springframework.lang.Nullable;
|
|||
|
||||
/**
|
||||
* Common interface for classes that can access named properties
|
||||
* (such as bean properties of an object or fields in an object)
|
||||
* Serves as base interface for {@link BeanWrapper}.
|
||||
* (such as bean properties of an object or fields in an object).
|
||||
*
|
||||
* <p>Serves as base interface for {@link BeanWrapper}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 1.1
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -51,18 +51,20 @@ import org.springframework.util.PatternMatchUtils;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Binder that allows for setting property values onto a target object,
|
||||
* including support for validation and binding result analysis.
|
||||
* The binding process can be customized through specifying allowed fields,
|
||||
* Binder that allows for setting property values on a target object, including
|
||||
* support for validation and binding result analysis.
|
||||
*
|
||||
* <p>The binding process can be customized by specifying allowed field patterns,
|
||||
* required fields, custom editors, etc.
|
||||
*
|
||||
* <p>Note that there are potential security implications in failing to set an array
|
||||
* of allowed fields. In the case of HTTP form POST data for example, malicious clients
|
||||
* can attempt to subvert an application by supplying values for fields or properties
|
||||
* that do not exist on the form. In some cases this could lead to illegal data being
|
||||
* set on command objects <i>or their nested objects</i>. For this reason, it is
|
||||
* <b>highly recommended to specify the {@link #setAllowedFields allowedFields} property</b>
|
||||
* on the DataBinder.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>The binding results can be examined via the {@link BindingResult} interface,
|
||||
* extending the {@link Errors} interface: see the {@link #getBindingResult()} method.
|
||||
|
@ -96,6 +98,7 @@ import org.springframework.util.StringUtils;
|
|||
* @author Rob Harrop
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
* @author Sam Brannen
|
||||
* @see #setAllowedFields
|
||||
* @see #setRequiredFields
|
||||
* @see #registerCustomEditor
|
||||
|
@ -418,15 +421,21 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register fields that should be allowed for binding. Default is all fields.
|
||||
* Restrict this for example to avoid unwanted modifications by malicious
|
||||
* Register field patterns that should be allowed for binding.
|
||||
* <p>Default is all fields.
|
||||
* <p>Restrict this for example to avoid unwanted modifications by malicious
|
||||
* users when binding HTTP request parameters.
|
||||
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
|
||||
* arbitrary number of pattern parts), as well as direct equality. More
|
||||
* sophisticated matching can be implemented by overriding the
|
||||
* {@code isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>disallowed</i> fields.
|
||||
* @param allowedFields array of field names
|
||||
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality.
|
||||
* <p>The default implementation of this method stores allowed field patterns
|
||||
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
|
||||
* form. Subclasses which override this method must therefore take this into
|
||||
* account.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>disallowed</i> field patterns.
|
||||
* @param allowedFields array of allowed field patterns
|
||||
* @see #setDisallowedFields
|
||||
* @see #isAllowed(String)
|
||||
*/
|
||||
|
@ -435,8 +444,9 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the fields that should be allowed for binding.
|
||||
* @return array of field names
|
||||
* Return the field patterns that should be allowed for binding.
|
||||
* @return array of allowed field patterns
|
||||
* @see #setAllowedFields(String...)
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getAllowedFields() {
|
||||
|
@ -444,25 +454,44 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register fields that should <i>not</i> be allowed for binding. Default
|
||||
* is none. Mark fields as disallowed for example to avoid unwanted
|
||||
* Register field patterns that should <i>not</i> be allowed for binding.
|
||||
* <p>Default is none.
|
||||
* <p>Mark fields as disallowed, for example to avoid unwanted
|
||||
* modifications by malicious users when binding HTTP request parameters.
|
||||
* <p>Supports "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
|
||||
* arbitrary number of pattern parts), as well as direct equality.
|
||||
* More sophisticated matching can be implemented by overriding the
|
||||
* {@code isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> fields.
|
||||
* @param disallowedFields array of field names
|
||||
* <p>Supports {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality.
|
||||
* <p>The default implementation of this method stores disallowed field patterns
|
||||
* in {@linkplain PropertyAccessorUtils#canonicalPropertyName(String) canonical}
|
||||
* form. As of Spring Framework 5.2.21, the default implementation also transforms
|
||||
* disallowed field patterns to {@linkplain String#toLowerCase() lowercase} to
|
||||
* support case-insensitive pattern matching in {@link #isAllowed}. Subclasses
|
||||
* which override this method must therefore take both of these transformations
|
||||
* into account.
|
||||
* <p>More sophisticated matching can be implemented by overriding the
|
||||
* {@link #isAllowed} method.
|
||||
* <p>Alternatively, specify a list of <i>allowed</i> field patterns.
|
||||
* @param disallowedFields array of disallowed field patterns
|
||||
* @see #setAllowedFields
|
||||
* @see #isAllowed(String)
|
||||
*/
|
||||
public void setDisallowedFields(@Nullable String... disallowedFields) {
|
||||
this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);
|
||||
if (disallowedFields == null) {
|
||||
this.disallowedFields = null;
|
||||
}
|
||||
else {
|
||||
String[] fieldPatterns = new String[disallowedFields.length];
|
||||
for (int i = 0; i < fieldPatterns.length; i++) {
|
||||
fieldPatterns[i] = PropertyAccessorUtils.canonicalPropertyName(disallowedFields[i]).toLowerCase();
|
||||
}
|
||||
this.disallowedFields = fieldPatterns;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fields that should <i>not</i> be allowed for binding.
|
||||
* @return array of field names
|
||||
* Return the field patterns that should <i>not</i> be allowed for binding.
|
||||
* @return array of disallowed field patterns
|
||||
* @see #setDisallowedFields(String...)
|
||||
*/
|
||||
@Nullable
|
||||
public String[] getDisallowedFields() {
|
||||
|
@ -774,16 +803,20 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return if the given field is allowed for binding.
|
||||
* Invoked for each passed-in property value.
|
||||
* <p>The default implementation checks for "xxx*", "*xxx", "*xxx*" and "xxx*yyy"
|
||||
* matches (with an arbitrary number of pattern parts), as well as direct equality,
|
||||
* in the specified lists of allowed fields and disallowed fields. A field matching
|
||||
* a disallowed pattern will not be accepted even if it also happens to match a
|
||||
* pattern in the allowed list.
|
||||
* <p>Can be overridden in subclasses.
|
||||
* Determine if the given field is allowed for binding.
|
||||
* <p>Invoked for each passed-in property value.
|
||||
* <p>Checks for {@code "xxx*"}, {@code "*xxx"}, {@code "*xxx*"}, and
|
||||
* {@code "xxx*yyy"} matches (with an arbitrary number of pattern parts), as
|
||||
* well as direct equality, in the configured lists of allowed field patterns
|
||||
* and disallowed field patterns.
|
||||
* <p>Matching against allowed field patterns is case-sensitive; whereas,
|
||||
* matching against disallowed field patterns is case-insensitive.
|
||||
* <p>A field matching a disallowed pattern will not be accepted even if it
|
||||
* also happens to match a pattern in the allowed list.
|
||||
* <p>Can be overridden in subclasses, but care must be taken to honor the
|
||||
* aforementioned contract.
|
||||
* @param field the field to check
|
||||
* @return if the field is allowed
|
||||
* @return {@code true} if the field is allowed
|
||||
* @see #setAllowedFields
|
||||
* @see #setDisallowedFields
|
||||
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
|
||||
|
@ -792,7 +825,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
|
|||
String[] allowed = getAllowedFields();
|
||||
String[] disallowed = getDisallowedFields();
|
||||
return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));
|
||||
(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field.toLowerCase())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,23 +64,26 @@ import org.springframework.format.support.DefaultFormattingConversionService;
|
|||
import org.springframework.format.support.FormattingConversionService;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.tests.sample.beans.BeanWithObjectProperty;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DataBinder}.
|
||||
*
|
||||
* @author Rod Johnson
|
||||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Kazuki Shimizu
|
||||
* @author Sam Brannen
|
||||
*/
|
||||
class DataBinderTests {
|
||||
|
||||
@Test
|
||||
void testBindingNoErrors() throws BindException {
|
||||
void bindingNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
|
@ -110,12 +113,11 @@ class DataBinderTests {
|
|||
assertThat(ex).isEqualTo(binder.getBindingResult());
|
||||
|
||||
other.reject("xxx");
|
||||
boolean condition = !other.equals(binder.getBindingResult());
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(other).isNotEqualTo(binder.getBindingResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
void bindingWithDefaultConversionNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
|
@ -131,7 +133,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testNestedBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
void nestedBindingWithDefaultConversionNoErrors() throws BindException {
|
||||
TestBean rod = new TestBean(new TestBean());
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
assertThat(binder.isIgnoreUnknownFields()).isTrue();
|
||||
|
@ -147,7 +149,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsNotIgnoreUnknown() {
|
||||
void bindingNoErrorsNotIgnoreUnknown() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setIgnoreUnknownFields(false);
|
||||
|
@ -160,7 +162,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsWithInvalidField() {
|
||||
void bindingNoErrorsWithInvalidField() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -171,7 +173,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingNoErrorsWithIgnoreInvalid() {
|
||||
void bindingNoErrorsWithIgnoreInvalid() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setIgnoreInvalidFields(true);
|
||||
|
@ -180,10 +182,14 @@ class DataBinderTests {
|
|||
pvs.add("spouse.age", 32);
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
|
||||
assertThat(rod.getName()).isEqualTo("Rod");
|
||||
assertThat(rod.getSpouse()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithErrors() {
|
||||
void bindingWithErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -245,7 +251,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithSystemFieldError() {
|
||||
void bindingWithSystemFieldError() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -257,7 +263,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithErrorsAndCustomEditors() {
|
||||
void bindingWithErrorsAndCustomEditors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.registerCustomEditor(String.class, "touchy", new PropertyEditorSupport() {
|
||||
|
@ -325,7 +331,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithCustomEditorOnObjectField() {
|
||||
void bindingWithCustomEditorOnObjectField() {
|
||||
BeanWithObjectProperty tb = new BeanWithObjectProperty();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.registerCustomEditor(Integer.class, "object", new CustomNumberEditor(Integer.class, true));
|
||||
|
@ -336,7 +342,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatter() {
|
||||
void bindingWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -368,7 +374,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatter() {
|
||||
void bindingErrorWithFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -391,7 +397,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithParseExceptionFromFormatter() {
|
||||
void bindingErrorWithParseExceptionFromFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -419,7 +425,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithRuntimeExceptionFromFormatter() {
|
||||
void bindingErrorWithRuntimeExceptionFromFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -447,7 +453,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatterAgainstList() {
|
||||
void bindingWithFormatterAgainstList() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -469,7 +475,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatterAgainstList() {
|
||||
void bindingErrorWithFormatterAgainstList() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -492,7 +498,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithFormatterAgainstFields() {
|
||||
void bindingWithFormatterAgainstFields() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
FormattingConversionService conversionService = new FormattingConversionService();
|
||||
|
@ -525,7 +531,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithFormatterAgainstFields() {
|
||||
void bindingErrorWithFormatterAgainstFields() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.initDirectFieldAccess();
|
||||
|
@ -549,7 +555,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithCustomFormatter() {
|
||||
void bindingWithCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.addCustomFormatter(new NumberStyleFormatter(), Float.class);
|
||||
|
@ -578,7 +584,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithCustomFormatter() {
|
||||
void bindingErrorWithCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.addCustomFormatter(new NumberStyleFormatter());
|
||||
|
@ -599,7 +605,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithParseExceptionFromCustomFormatter() {
|
||||
void bindingErrorWithParseExceptionFromCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
|
||||
|
@ -624,7 +630,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrorWithRuntimeExceptionFromCustomFormatter() {
|
||||
void bindingErrorWithRuntimeExceptionFromCustomFormatter() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
|
||||
|
@ -649,7 +655,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testConversionWithInappropriateStringEditor() {
|
||||
void conversionWithInappropriateStringEditor() {
|
||||
DataBinder dataBinder = new DataBinder(null);
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
dataBinder.setConversionService(conversionService);
|
||||
|
@ -662,7 +668,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedFields() throws BindException {
|
||||
void bindingWithAllowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "myparam");
|
||||
|
@ -672,30 +678,32 @@ class DataBinderTests {
|
|||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithDisallowedFields() throws BindException {
|
||||
void bindingWithDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setDisallowedFields("age");
|
||||
binder.setDisallowedFields(" ", "\t", "favouriteColour", null, "age");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("age", "32x");
|
||||
pvs.add("favouriteColour", "BLUE");
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields.length).isEqualTo(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(rod.getFavouriteColour()).as("did not change favourite colour").isNull();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactlyInAnyOrder("age", "favouriteColour");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedAndDisallowedFields() throws BindException {
|
||||
void bindingWithAllowedAndDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "myparam");
|
||||
|
@ -706,34 +714,32 @@ class DataBinderTests {
|
|||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
|
||||
void bindingWithOverlappingAllowedAndDisallowedFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("name", "age");
|
||||
binder.setDisallowedFields("age");
|
||||
binder.setDisallowedFields("AGE");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("name", "Rod");
|
||||
pvs.add("age", "32x");
|
||||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getName().equals("Rod")).as("changed name correctly").isTrue();
|
||||
assertThat(rod.getAge() == 0).as("did not change age").isTrue();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("age");
|
||||
|
||||
assertThat(rod.getName()).as("changed name correctly").isEqualTo("Rod");
|
||||
assertThat(rod.getAge()).as("did not change age").isZero();
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("age");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedFieldsUsingAsterisks() throws BindException {
|
||||
void bindingWithAllowedFieldsUsingAsterisks() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
binder.setAllowedFields("nam*", "*ouchy");
|
||||
|
@ -760,11 +766,11 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithAllowedAndDisallowedMapFields() throws BindException {
|
||||
void bindingWithAllowedAndDisallowedMapFields() throws BindException {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod);
|
||||
binder.setAllowedFields("someMap[key1]", "someMap[key2]");
|
||||
binder.setDisallowedFields("someMap['key3']", "someMap[key4]");
|
||||
binder.setDisallowedFields("someMap['KEY3']", "SomeMap[key4]");
|
||||
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("someMap[key1]", "value1");
|
||||
|
@ -774,21 +780,18 @@ class DataBinderTests {
|
|||
|
||||
binder.bind(pvs);
|
||||
binder.close();
|
||||
assertThat(rod.getSomeMap().get("key1")).isEqualTo("value1");
|
||||
assertThat(rod.getSomeMap().get("key2")).isEqualTo("value2");
|
||||
assertThat(rod.getSomeMap().get("key3")).isNull();
|
||||
assertThat(rod.getSomeMap().get("key4")).isNull();
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(2);
|
||||
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key3]")).isTrue();
|
||||
assertThat(ObjectUtils.containsElement(disallowedFields, "someMap[key4]")).isTrue();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> someMap = (Map<String, String>) rod.getSomeMap();
|
||||
assertThat(someMap).containsOnly(entry("key1", "value1"), entry("key2", "value2"));
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("someMap[key3]", "someMap[key4]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for required field, both null, non-existing and empty strings.
|
||||
*/
|
||||
@Test
|
||||
void testBindingWithRequiredFields() {
|
||||
void bindingWithRequiredFields() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
|
@ -819,7 +822,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithRequiredMapFields() {
|
||||
void bindingWithRequiredMapFields() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
|
@ -839,7 +842,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingWithNestedObjectCreation() {
|
||||
void bindingWithNestedObjectCreation() {
|
||||
TestBean tb = new TestBean();
|
||||
|
||||
DataBinder binder = new DataBinder(tb, "person");
|
||||
|
@ -860,7 +863,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorWithOldValueAccess() {
|
||||
void customEditorWithOldValueAccess() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
|
@ -885,7 +888,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForSingleProperty() {
|
||||
void customEditorForSingleProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
@ -925,7 +928,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForPrimitiveProperty() {
|
||||
void customEditorForPrimitiveProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
|
@ -949,7 +952,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorForAllStringProperties() {
|
||||
void customEditorForAllStringProperties() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
|
@ -981,7 +984,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForSingleProperty() {
|
||||
void customFormatterForSingleProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
@ -1021,7 +1024,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForPrimitiveProperty() {
|
||||
void customFormatterForPrimitiveProperty() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
|
@ -1045,7 +1048,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomFormatterForAllStringProperties() {
|
||||
void customFormatterForAllStringProperties() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
|
||||
|
@ -1077,7 +1080,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testJavaBeanPropertyConventions() {
|
||||
void javaBeanPropertyConventions() {
|
||||
Book book = new Book();
|
||||
DataBinder binder = new DataBinder(book);
|
||||
|
||||
|
@ -1101,7 +1104,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testOptionalProperty() {
|
||||
void optionalProperty() {
|
||||
OptionalHolder bean = new OptionalHolder();
|
||||
DataBinder binder = new DataBinder(bean);
|
||||
binder.setConversionService(new DefaultConversionService());
|
||||
|
@ -1122,7 +1125,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testValidatorNoErrors() throws Exception {
|
||||
void validatorNoErrors() throws Exception {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setAge(33);
|
||||
tb.setName("Rod");
|
||||
|
@ -1175,15 +1178,13 @@ class DataBinderTests {
|
|||
assertThat(errors.getNestedPath()).isEqualTo("spouse.");
|
||||
|
||||
assertThat(errors.getErrorCount()).isEqualTo(1);
|
||||
boolean condition1 = !errors.hasGlobalErrors();
|
||||
assertThat(condition1).isTrue();
|
||||
assertThat(errors.hasGlobalErrors()).isFalse();
|
||||
assertThat(errors.getFieldErrorCount("age")).isEqualTo(1);
|
||||
boolean condition = !errors.hasFieldErrors("name");
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(errors.hasFieldErrors("name")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithErrors() {
|
||||
void validatorWithErrors() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
|
@ -1252,7 +1253,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithErrorsAndCodesPrefix() {
|
||||
void validatorWithErrorsAndCodesPrefix() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setSpouse(new TestBean());
|
||||
|
||||
|
@ -1324,7 +1325,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testValidatorWithNestedObjectNull() {
|
||||
void validatorWithNestedObjectNull() {
|
||||
TestBean tb = new TestBean();
|
||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||
Validator testValidator = new TestBeanValidator();
|
||||
|
@ -1343,7 +1344,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testNestedValidatorWithoutNestedPath() {
|
||||
void nestedValidatorWithoutNestedPath() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("XXX");
|
||||
Errors errors = new BeanPropertyBindingResult(tb, "tb");
|
||||
|
@ -1357,7 +1358,8 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingStringArrayToIntegerSet() {
|
||||
@SuppressWarnings("unchecked")
|
||||
void bindingStringArrayToIntegerSet() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class) {
|
||||
|
@ -1371,12 +1373,8 @@ class DataBinderTests {
|
|||
binder.bind(pvs);
|
||||
|
||||
assertThat(binder.getBindingResult().getFieldValue("set")).isEqualTo(tb.getSet());
|
||||
boolean condition = tb.getSet() instanceof TreeSet;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getSet().size()).isEqualTo(3);
|
||||
assertThat(tb.getSet().contains(10)).isTrue();
|
||||
assertThat(tb.getSet().contains(20)).isTrue();
|
||||
assertThat(tb.getSet().contains(30)).isTrue();
|
||||
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
|
||||
assertThat((Set<Integer>) tb.getSet()).containsExactly(10, 20, 30);
|
||||
|
||||
pvs = new MutablePropertyValues();
|
||||
pvs.add("set", null);
|
||||
|
@ -1386,7 +1384,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingNullToEmptyCollection() {
|
||||
void bindingNullToEmptyCollection() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(Set.class, new CustomCollectionEditor(TreeSet.class, true));
|
||||
|
@ -1394,13 +1392,12 @@ class DataBinderTests {
|
|||
pvs.add("set", null);
|
||||
binder.bind(pvs);
|
||||
|
||||
boolean condition = tb.getSet() instanceof TreeSet;
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getSet().isEmpty()).isTrue();
|
||||
assertThat(tb.getSet()).isInstanceOf(TreeSet.class);
|
||||
assertThat(tb.getSet()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingToIndexedField() {
|
||||
void bindingToIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String.class, "array.name", new PropertyEditorSupport() {
|
||||
|
@ -1439,7 +1436,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindingToNestedIndexedField() {
|
||||
void bindingToNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
|
@ -1470,7 +1467,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testEditorForNestedIndexedField() {
|
||||
void editorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
|
@ -1496,7 +1493,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testSpecificEditorForNestedIndexedField() {
|
||||
void specificEditorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
|
@ -1522,7 +1519,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testInnerSpecificEditorForNestedIndexedField() {
|
||||
void innerSpecificEditorForNestedIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
tb.getArray()[0].setNestedIndexedBean(new IndexedTestBean());
|
||||
tb.getArray()[1].setNestedIndexedBean(new IndexedTestBean());
|
||||
|
@ -1548,7 +1545,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToIndexedField() {
|
||||
void directBindingToIndexedField() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "array", new PropertyEditorSupport() {
|
||||
|
@ -1601,7 +1598,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
|
||||
void directBindingToEmptyIndexedFieldWithRegisteredSpecificEditor() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "map[key0]", new PropertyEditorSupport() {
|
||||
|
@ -1632,7 +1629,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testDirectBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
|
||||
void directBindingToEmptyIndexedFieldWithRegisteredGenericEditor() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, "map", new PropertyEditorSupport() {
|
||||
|
@ -1663,7 +1660,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testCustomEditorWithSubclass() {
|
||||
void customEditorWithSubclass() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(TestBean.class, new PropertyEditorSupport() {
|
||||
|
@ -1697,7 +1694,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindToStringArrayWithArrayEditor() {
|
||||
void bindToStringArrayWithArrayEditor() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String[].class, "stringArray", new PropertyEditorSupport() {
|
||||
|
@ -1709,15 +1706,12 @@ class DataBinderTests {
|
|||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("stringArray", "a1-b2");
|
||||
binder.bind(pvs);
|
||||
boolean condition = !binder.getBindingResult().hasErrors();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(tb.getStringArray().length).isEqualTo(2);
|
||||
assertThat(tb.getStringArray()[0]).isEqualTo("a1");
|
||||
assertThat(tb.getStringArray()[1]).isEqualTo("b2");
|
||||
assertThat(binder.getBindingResult().hasErrors()).isFalse();
|
||||
assertThat(tb.getStringArray()).containsExactly("a1", "b2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindToStringArrayWithComponentEditor() {
|
||||
void bindToStringArrayWithComponentEditor() {
|
||||
TestBean tb = new TestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
binder.registerCustomEditor(String.class, "stringArray", new PropertyEditorSupport() {
|
||||
|
@ -1729,15 +1723,14 @@ class DataBinderTests {
|
|||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
pvs.add("stringArray", new String[] {"a1", "b2"});
|
||||
binder.bind(pvs);
|
||||
boolean condition = !binder.getBindingResult().hasErrors();
|
||||
assertThat(condition).isTrue();
|
||||
assertThat(binder.getBindingResult().hasErrors()).isFalse();
|
||||
assertThat(tb.getStringArray().length).isEqualTo(2);
|
||||
assertThat(tb.getStringArray()[0]).isEqualTo("Xa1");
|
||||
assertThat(tb.getStringArray()[1]).isEqualTo("Xb2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBindingErrors() {
|
||||
void bindingErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -1764,7 +1757,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAddAllErrors() {
|
||||
void addAllErrors() {
|
||||
TestBean rod = new TestBean();
|
||||
DataBinder binder = new DataBinder(rod, "person");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -1784,7 +1777,7 @@ class DataBinderTests {
|
|||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testBindingWithResortedList() {
|
||||
void bindingWithResortedList() {
|
||||
IndexedTestBean tb = new IndexedTestBean();
|
||||
DataBinder binder = new DataBinder(tb, "tb");
|
||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||
|
@ -1802,7 +1795,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testRejectWithoutDefaultMessage() {
|
||||
void rejectWithoutDefaultMessage() {
|
||||
TestBean tb = new TestBean();
|
||||
tb.setName("myName");
|
||||
tb.setAge(99);
|
||||
|
@ -1820,7 +1813,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testBindExceptionSerializable() throws Exception {
|
||||
void bindExceptionSerializable() throws Exception {
|
||||
SerializablePerson tb = new SerializablePerson();
|
||||
tb.setName("myName");
|
||||
tb.setAge(99);
|
||||
|
@ -1849,27 +1842,27 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testTrackDisallowedFields() {
|
||||
void trackDisallowedFields() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAllowedFields("name", "age");
|
||||
|
||||
String name = "Rob Harrop";
|
||||
String beanName = "foobar";
|
||||
int age = 42;
|
||||
|
||||
MutablePropertyValues mpvs = new MutablePropertyValues();
|
||||
mpvs.add("name", name);
|
||||
mpvs.add("beanName", beanName);
|
||||
mpvs.add("age", age);
|
||||
mpvs.add("beanName", "foobar");
|
||||
binder.bind(mpvs);
|
||||
|
||||
assertThat(testBean.getName()).isEqualTo(name);
|
||||
String[] disallowedFields = binder.getBindingResult().getSuppressedFields();
|
||||
assertThat(disallowedFields).hasSize(1);
|
||||
assertThat(disallowedFields[0]).isEqualTo("beanName");
|
||||
assertThat(testBean.getAge()).isEqualTo(age);
|
||||
assertThat(binder.getBindingResult().getSuppressedFields()).containsExactly("beanName");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowWithinDefaultLimit() {
|
||||
void autoGrowWithinDefaultLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
|
||||
|
@ -1881,7 +1874,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowBeyondDefaultLimit() {
|
||||
void autoGrowBeyondDefaultLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
|
||||
|
@ -1894,7 +1887,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowWithinCustomLimit() {
|
||||
void autoGrowWithinCustomLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAutoGrowCollectionLimit(10);
|
||||
|
@ -1907,7 +1900,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testAutoGrowBeyondCustomLimit() {
|
||||
void autoGrowBeyondCustomLimit() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setAutoGrowCollectionLimit(10);
|
||||
|
@ -1921,7 +1914,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testNestedGrowingList() {
|
||||
void nestedGrowingList() {
|
||||
Form form = new Form();
|
||||
DataBinder binder = new DataBinder(form, "form");
|
||||
MutablePropertyValues mpv = new MutablePropertyValues();
|
||||
|
@ -1937,7 +1930,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testFieldErrorAccessVariations() {
|
||||
void fieldErrorAccessVariations() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
assertThat(binder.getBindingResult().getGlobalError()).isNull();
|
||||
|
@ -1958,7 +1951,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-14888
|
||||
void testSetAutoGrowCollectionLimit() {
|
||||
void setAutoGrowCollectionLimit() {
|
||||
BeanWithIntegerList tb = new BeanWithIntegerList();
|
||||
DataBinder binder = new DataBinder(tb);
|
||||
binder.setAutoGrowCollectionLimit(257);
|
||||
|
@ -1972,7 +1965,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-14888
|
||||
void testSetAutoGrowCollectionLimitAfterInitialization() {
|
||||
void setAutoGrowCollectionLimitAfterInitialization() {
|
||||
DataBinder binder = new DataBinder(new BeanWithIntegerList());
|
||||
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
|
||||
assertThatIllegalStateException().isThrownBy(() ->
|
||||
|
@ -1981,7 +1974,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
|
||||
void setCustomMessageCodesResolverBeforeInitializeBindingResultForBeanPropertyAccess() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
|
||||
|
@ -1998,7 +1991,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
|
||||
void setCustomMessageCodesResolverBeforeInitializeBindingResultForDirectFieldAccess() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
DefaultMessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
|
||||
|
@ -2013,7 +2006,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetCustomMessageCodesResolverAfterInitializeBindingResult() {
|
||||
void setCustomMessageCodesResolverAfterInitializeBindingResult() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.initBeanPropertyAccess();
|
||||
|
@ -2028,7 +2021,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testSetMessageCodesResolverIsNullAfterInitializeBindingResult() {
|
||||
void setMessageCodesResolverIsNullAfterInitializeBindingResult() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.initBeanPropertyAccess();
|
||||
|
@ -2042,8 +2035,7 @@ class DataBinderTests {
|
|||
}
|
||||
|
||||
@Test // SPR-15009
|
||||
void testCallSetMessageCodesResolverTwice() {
|
||||
|
||||
void callSetMessageCodesResolverTwice() {
|
||||
TestBean testBean = new TestBean();
|
||||
DataBinder binder = new DataBinder(testBean, "testBean");
|
||||
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -33,6 +33,15 @@ import org.springframework.web.util.WebUtils;
|
|||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||
* from servlet request parameters to JavaBeans, including support for multipart files.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
|
||||
* which include specifying allowed/required fields, and registering custom
|
||||
* property editors.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-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,6 +34,15 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
* the Servlet API; serves as base class for more specific DataBinder variants,
|
||||
* such as {@link org.springframework.web.bind.ServletRequestDataBinder}.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>Includes support for field markers which address a common problem with
|
||||
* HTML checkboxes and select options: detecting that a field was part of
|
||||
* the form, but did not generate a request parameter because it was empty.
|
||||
|
|
|
@ -100,6 +100,7 @@ import java.lang.annotation.Target;
|
|||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
* @see ControllerAdvice
|
||||
* @see org.springframework.web.context.request.WebRequest
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -23,15 +23,24 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that identifies methods which initialize the
|
||||
* Annotation that identifies methods that initialize the
|
||||
* {@link org.springframework.web.bind.WebDataBinder} which
|
||||
* will be used for populating command and form object arguments
|
||||
* of annotated handler methods.
|
||||
*
|
||||
* <p>Such init-binder methods support all arguments that {@link RequestMapping}
|
||||
* supports, except for command/form objects and corresponding validation result
|
||||
* objects. Init-binder methods must not have a return value; they are usually
|
||||
* declared as {@code void}.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>{@code @InitBinder} methods support all arguments that
|
||||
* {@link RequestMapping @RequestMapping} methods support, except for command/form
|
||||
* objects and corresponding validation result objects. {@code @InitBinder} methods
|
||||
* must not have a return value; they are usually declared as {@code void}.
|
||||
*
|
||||
* <p>Typical arguments are {@link org.springframework.web.bind.WebDataBinder}
|
||||
* in combination with {@link org.springframework.web.context.request.WebRequest}
|
||||
|
@ -39,6 +48,7 @@ import java.lang.annotation.Target;
|
|||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
* @see ControllerAdvice
|
||||
* @see org.springframework.web.bind.WebDataBinder
|
||||
* @see org.springframework.web.context.request.WebRequest
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -31,18 +31,27 @@ import org.springframework.ui.Model;
|
|||
* for controller classes with {@link RequestMapping @RequestMapping}
|
||||
* methods.
|
||||
*
|
||||
* <p>Can be used to expose command objects to a web view, using
|
||||
* specific attribute names, through annotating corresponding
|
||||
* parameters of an {@link RequestMapping @RequestMapping} method.
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>Can also be used to expose reference data to a web view
|
||||
* through annotating accessor methods in a controller class with
|
||||
* <p>{@code @ModelAttribute} can be used to expose command objects to a web view,
|
||||
* using specific attribute names, by annotating corresponding parameters of an
|
||||
* {@link RequestMapping @RequestMapping} method.
|
||||
*
|
||||
* <p>{@code @ModelAttribute} can also be used to expose reference data to a web
|
||||
* view by annotating accessor methods in a controller class with
|
||||
* {@link RequestMapping @RequestMapping} methods. Such accessor
|
||||
* methods are allowed to have any arguments that
|
||||
* {@link RequestMapping @RequestMapping} methods support, returning
|
||||
* the model attribute value to expose.
|
||||
*
|
||||
* <p>Note however that reference data and all other model content is
|
||||
* <p>Note however that reference data and all other model content are
|
||||
* not available to web views when request processing results in an
|
||||
* {@code Exception} since the exception could be raised at any time
|
||||
* making the content of the model unreliable. For this reason
|
||||
|
@ -52,6 +61,7 @@ import org.springframework.ui.Model;
|
|||
* @author Juergen Hoeller
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 2.5
|
||||
* @see ControllerAdvice
|
||||
*/
|
||||
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -77,7 +87,7 @@ public @interface ModelAttribute {
|
|||
String name() default "";
|
||||
|
||||
/**
|
||||
* Allows declaring data binding disabled directly on an {@code @ModelAttribute}
|
||||
* Allows data binding to be disabled directly on an {@code @ModelAttribute}
|
||||
* method parameter or on the attribute returned from an {@code @ModelAttribute}
|
||||
* method, both of which would prevent data binding for that attribute.
|
||||
* <p>By default this is set to {@code true} in which case data binding applies.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -36,6 +36,15 @@ import org.springframework.web.server.ServerWebExchange;
|
|||
* Specialized {@link org.springframework.validation.DataBinder} to perform data
|
||||
* binding from URL query parameters or form data in the request data to Java objects.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -35,6 +35,15 @@ import org.springframework.web.multipart.support.StandardServletPartUtils;
|
|||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||
* from web request parameters to JavaBeans, including support for multipart files.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* <p>See the DataBinder/WebDataBinder superclasses for customization options,
|
||||
* which include specifying allowed/required fields, and registering custom
|
||||
* property editors.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -116,7 +116,7 @@ public class InitBinderDataBinderFactoryTests {
|
|||
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
|
||||
|
||||
assertThat(dataBinder.getDisallowedFields()).isNotNull();
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
|
||||
}
|
||||
|
||||
private WebDataBinderFactory createFactory(String methodName, Class<?>... parameterTypes)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -121,7 +121,7 @@ public class InitBinderBindingContextTests {
|
|||
WebDataBinder dataBinder = context.createDataBinder(exchange, null, "foo");
|
||||
|
||||
assertThat(dataBinder.getDisallowedFields()).isNotNull();
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualTo("requestParam-22");
|
||||
assertThat(dataBinder.getDisallowedFields()[0]).isEqualToIgnoringCase("requestParam-22");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
@ -29,6 +29,15 @@ import org.springframework.web.servlet.HandlerMapping;
|
|||
* Subclass of {@link ServletRequestDataBinder} that adds URI template variables
|
||||
* to the values used for data binding.
|
||||
*
|
||||
* <p><strong>WARNING</strong>: Data binding can lead to security issues by exposing
|
||||
* parts of the object graph that are not meant to be accessed or modified by
|
||||
* external clients. Therefore the design and use of data binding should be considered
|
||||
* carefully with regard to security. For more details, please refer to the dedicated
|
||||
* sections on data binding for
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-initbinder-model-design">Spring Web MVC</a> and
|
||||
* <a href="https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-ann-initbinder-model-design">Spring WebFlux</a>
|
||||
* in the reference manual.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
* @see ServletRequestDataBinder
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
In the context of web applications, _data binding_ involves the binding of HTTP request
|
||||
parameters (that is, form data or query parameters) to properties in a model object and
|
||||
its nested objects.
|
||||
|
||||
Only `public` properties following the
|
||||
https://www.oracle.com/java/technologies/javase/javabeans-spec.html[JavaBeans naming conventions]
|
||||
are exposed for data binding — for example, `public String getFirstName()` and
|
||||
`public void setFirstName(String)` methods for a `firstName` property.
|
||||
|
||||
TIP: The model object, and its nested object graph, is also sometimes referred to as a
|
||||
_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object).
|
||||
|
||||
By default, Spring permits binding to all public properties in the model object graph.
|
||||
This means you need to carefully consider what public properties the model has, since a
|
||||
client could target any public property path, even some that are not expected to be
|
||||
targeted for a given use case.
|
||||
|
||||
For example, given an HTTP form data endpoint, a malicious client could supply values for
|
||||
properties that exist in the model object graph but are not part of the HTML form
|
||||
presented in the browser. This could lead to data being set on the model object and any
|
||||
of its nested objects, that is not expected to be updated.
|
||||
|
||||
The recommended approach is to use a _dedicated model object_ that exposes only
|
||||
properties that are relevant for the form submission. For example, on a form for changing
|
||||
a user's email address, the model object should declare a minimum set of properties such
|
||||
as in the following `ChangeEmailForm`.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public class ChangeEmailForm {
|
||||
|
||||
private String oldEmailAddress;
|
||||
private String newEmailAddress;
|
||||
|
||||
public void setOldEmailAddress(String oldEmailAddress) {
|
||||
this.oldEmailAddress = oldEmailAddress;
|
||||
}
|
||||
|
||||
public String getOldEmailAddress() {
|
||||
return this.oldEmailAddress;
|
||||
}
|
||||
|
||||
public void setNewEmailAddress(String newEmailAddress) {
|
||||
this.newEmailAddress = newEmailAddress;
|
||||
}
|
||||
|
||||
public String getNewEmailAddress() {
|
||||
return this.newEmailAddress;
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
If you cannot or do not want to use a _dedicated model object_ for each data
|
||||
binding use case, you **must** limit the properties that are allowed for data binding.
|
||||
Ideally, you can achieve this by registering _allowed field patterns_ via the
|
||||
`setAllowedFields()` method on `WebDataBinder`.
|
||||
|
||||
For example, to register allowed field patterns in your application, you can implement an
|
||||
`@InitBinder` method in a `@Controller` or `@ControllerAdvice` component as shown below:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
@Controller
|
||||
public class ChangeEmailController {
|
||||
|
||||
@InitBinder
|
||||
void initBinder(WebDataBinder binder) {
|
||||
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
|
||||
}
|
||||
|
||||
// @RequestMapping methods, etc.
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
In addition to registering allowed patterns, it is also possible to register _disallowed
|
||||
field patterns_ via the `setDisallowedFields()` method in `DataBinder` and its subclasses.
|
||||
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
|
||||
`setAllowedFields()` should be favored over `setDisallowedFields()`.
|
||||
|
||||
Note that matching against allowed field patterns is case-sensitive; whereas, matching
|
||||
against disallowed field patterns is case-insensitive. In addition, a field matching a
|
||||
disallowed pattern will not be accepted even if it also happens to match a pattern in the
|
||||
allowed list.
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
It is extremely important to properly configure allowed and disallowed field patterns
|
||||
when exposing your domain model directly for data binding purposes. Otherwise, it is a
|
||||
big security risk.
|
||||
|
||||
Furthermore, it is strongly recommended that you do **not** use types from your domain
|
||||
model such as JPA or Hibernate entities as the model object in data binding scenarios.
|
||||
====
|
|
@ -3342,6 +3342,11 @@ controller-specific `Formatter` instances, as the following example shows:
|
|||
----
|
||||
<1> Adding a custom formatter (a `DateFormatter`, in this case).
|
||||
|
||||
[[webflux-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web.adoc#mvc-ann-initbinder-model-design, Web MVC>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[webflux-ann-controller-exceptions]]
|
||||
|
|
|
@ -3757,6 +3757,13 @@ controller-specific `Formatter` implementations, as the following example shows:
|
|||
----
|
||||
<1> Defining an `@InitBinder` method on a custom formatter.
|
||||
|
||||
[[mvc-ann-initbinder-model-design]]
|
||||
==== Model Design
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-initbinder-model-design, WebFlux>>#
|
||||
|
||||
include::web-data-binding-model-design.adoc[]
|
||||
|
||||
|
||||
[[mvc-ann-exceptionhandler]]
|
||||
=== Exceptions
|
||||
[.small]#<<web-reactive.adoc#webflux-ann-controller-exceptions, WebFlux>>#
|
||||
|
|
Loading…
Reference in New Issue