Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,23 @@ class SimpleDataBinder implements DataBinder {
}

protected boolean isOkToBind(String propName, List whiteList, List blackList) {
'class' != propName && 'classLoader' != propName && 'protectionDomain' != propName && 'metaClass' != propName && 'metaPropertyValues' != propName && 'properties' != propName && !blackList?.contains(propName) && (whiteList == null || isBindAllBindingIncludeList(whiteList) || whiteList.contains(propName) || whiteList.find { it -> it?.toString()?.startsWith(propName + '.') })
!isFrameworkProperty(propName) && !blackList?.contains(propName) &&
(whiteList == null || isBindAllBindingIncludeList(whiteList) || whiteList.contains(propName) ||
whiteList.any { item -> item?.toString()?.startsWith(propName + '.') })
}

static boolean isPropertyExcluded(String propertyName, List excludeList) {
excludeList?.any { item ->
String excludeName = item?.toString()
excludeName == propertyName || propertyName.startsWith(excludeName + '.') ||
(excludeName?.endsWith('.*') && propertyName.startsWith(excludeName.substring(0, excludeName.length() - 1))) ||
(excludeName?.endsWith('_*') && propertyName.startsWith(excludeName.substring(0, excludeName.length() - 1)))
} ?: false
}

private static boolean isFrameworkProperty(String propertyName) {
'class' == propertyName || 'classLoader' == propertyName || 'protectionDomain' == propertyName ||
'metaClass' == propertyName || 'metaPropertyValues' == propertyName || 'properties' == propertyName
}

protected static List getBindAllBindingIncludeList() {
Expand Down
3 changes: 3 additions & 0 deletions grails-doc/src/en/guide/upgrading/upgrading80x.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,9 @@ The Spring annotations still work, so this is non-blocking, but new code should
* **Namespaced link generation is namespace-aware.**
When a link, form action, pagination link, sortable column link, redirect, chain, or include targets a controller without an explicit `namespace`, Grails now resolves the namespace automatically. In the normal case, where only one controller has the target name, `controller` and `action` generate the correct namespaced or non-namespaced URL. Ambiguity only occurs when multiple controllers share the same name. In that case, specify `namespace` to choose the target explicitly. Pass `namespace: null` from Groovy code or `namespace=""` in a GSP tag to target the non-namespaced controller explicitly.

* **`bindData` can clear omitted included fields.**
`bindData(target, source, [include: [...], nullMissing: true])` now assigns `null` to included properties that are absent from the binding source. The behavior is opt-in, requires an `include` list, and does not apply globally. Existing `bindData` calls without `nullMissing: true` keep omitted fields unchanged.

==== 21. Tag Library Test Cleanup Changes

Grails 8 removes the `purgeTagLibMetaClass` test hook used by some web and TagLib unit tests.
Expand Down
7 changes: 6 additions & 1 deletion grails-doc/src/en/ref/Controllers/bindData.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ bindData(target, params, [exclude: ['firstName', 'lastName']], "author")

// using inclusive map
bindData(target, params, [include: ['firstName', 'lastName']], "author")

// clear included properties omitted from the source
bindData(target, params, [include: ['firstName', 'lastName'], nullMissing: true])
----


Expand All @@ -57,7 +60,7 @@ Arguments:

* `target` - The target object to bind to
* `params` - A `Map` of source parameters, often the link:params.html[params] object when used in a controller
* `includesExcludes` - (Optional) A map with 'include' and/or 'exclude' lists containing the names of properties to either include or exclude.
* `includesExcludes` - (Optional) A map with 'include' and/or 'exclude' lists containing the names of properties to either include or exclude. Set `nullMissing: true` with an `include` list to assign `null` to included properties that are omitted from the binding source.
* `prefix` - (Optional) A string representing a prefix to use to filter parameters. The method will automatically append a '.' when matching the prefix to parameters, so you can use 'author' to filter for parameters such as 'author.name'.

If no `include` list is supplied, `bindData` uses the target class default binding behavior. By default, statically typed instance properties bind for compatibility unless they are marked `bindable: false`. Existing `bindable: true` declarations and explicit `include` lists continue to bind exactly the properties they name without configuration changes. An empty `include` list binds no properties.
Expand All @@ -84,6 +87,8 @@ class PersonController {

See the link:{constraintsRefFromRef}bindable.html[bindable] constraint documentation for more information on controlling default bindability. Applications may set `grails.databinding.legacyBindableDefault=false` to opt into deny-by-default binding allowlists. In secure mode, permit a property with `bindable: true`, an explicit `include` list, or `@BindAllowed` on a controller action command object parameter.

`nullMissing` is opt-in and only applies when an `include` list is provided. This is useful for update forms where an omitted allowed field should clear an existing value instead of leaving stale persisted data. Excluded properties are not cleared.

The underlying implementation uses Spring's Data Binding framework. If the target is a domain class, type conversion errors are stored in the `errors` property of the domain class.

Refer to the section on link:{guidePath}theWebLayer.html#dataBinding[data binding] in the user guide for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.grails.web.binding

import grails.config.Config
import grails.gorm.dirty.checking.DirtyCheck
import grails.persistence.Entity
import grails.util.Holders
Expand All @@ -26,11 +27,19 @@ import org.grails.config.PropertySourcesConfig
import org.grails.validation.ConstraintEvalUtils
import org.grails.web.databinding.DefaultASTDatabindingHelper
import spock.lang.Issue
import spock.lang.Shared
import spock.lang.Specification

class DefaultASTDatabindingHelperDomainClassSpecialPropertiesSpec extends
Specification {

@Shared
Config originalConfig

def setupSpec() {
originalConfig = Holders.config
}

def setup() {
ConstraintEvalUtils.clearDefaultConstraints()
Holders.setConfig(new PropertySourcesConfig([(DefaultASTDatabindingHelper.LEGACY_BINDABLE_DEFAULT): false]))
Expand All @@ -40,6 +49,11 @@ class DefaultASTDatabindingHelperDomainClassSpecialPropertiesSpec extends
ConstraintEvalUtils.clearDefaultConstraints()
}

def cleanupSpec() {
ConstraintEvalUtils.clearDefaultConstraints()
Holders.setConfig(originalConfig)
}

@Issue('GRAILS-11173')
void 'Test binding to special properties in a domain class'() {
when:
Expand Down
Loading
Loading