Skip to content

Commit 05f795b

Browse files
committed
HV-1816 Limit the EL features exposed by default
1 parent 40c5c64 commit 05f795b

File tree

42 files changed

+1618
-98
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1618
-98
lines changed

documentation/src/main/asciidoc/ch04.asciidoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@ context:
9191
`format(String format, Object... args)` which behaves like
9292
`java.util.Formatter.format(String format, Object... args)`.
9393

94+
Expression Language is very flexible and Hibernate Validator offers several feature levels
95+
that you can use to enable Expression Language features through the `ExpressionLanguageFeatureLevel` enum:
96+
97+
* `NONE`: Expression Language interpolation is fully disabled.
98+
* `VARIABLES`: Allow interpolation of the variables injected via `addExpressionVariable()`, resources bundles and usage of the `formatter` object.
99+
* `BEAN_PROPERTIES`: Allow everything `VARIABLES` allows plus the interpolation of bean properties.
100+
* `BEAN_METHODS`: Also allow execution of bean methods. Can be considered safe for hardcoded constraint messages but not for <<section-hibernateconstraintvalidatorcontext, custom violations>>
101+
where extra care is required.
102+
103+
The default feature level for constraint messages is `BEAN_PROPERTIES`.
104+
105+
You can define the Expression Language feature level when <<el-features, bootstrapping the `ValidatorFactory`>>.
106+
94107
The following section provides several examples for using EL expressions in error messages.
95108

96109
==== Examples

documentation/src/main/asciidoc/ch12.asciidoc

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,55 @@ include::{sourcedir}/org/hibernate/validator/referenceguide/chapter12/dynamicpay
419419
----
420420
====
421421

422+
[[el-features]]
423+
=== Enabling Expression Language features
424+
425+
Hibernate Validator restricts the Expression Language features exposed by default.
426+
427+
For this purpose, we define several feature levels in `ExpressionLanguageFeatureLevel`:
428+
429+
* `NONE`: Expression Language interpolation is fully disabled.
430+
* `VARIABLES`: Allow interpolation of the variables injected via `addExpressionVariable()`, resources bundles and usage of the `formatter` object.
431+
* `BEAN_PROPERTIES`: Allow everything `VARIABLES` allows plus the interpolation of bean properties.
432+
* `BEAN_METHODS`: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.
433+
434+
Depending on the context, the features we expose are different:
435+
436+
* For constraints, the default level is `BEAN_PROPERTIES`.
437+
For all the built-in constraint messages to be correctly interpolated, you need at least the `VARIABLES` level.
438+
* For custom violations, created via the `ConstraintValidatorContext`, Expression Language is disabled by default.
439+
You can enable it for specific custom violations and, when enabled, it will default to `VARIABLES`.
440+
441+
Hibernate Validator provides ways to override these defaults when boostrapping the `ValidatorFactory`.
442+
443+
To change the Expression Language feature level for constraints, use the following:
444+
445+
[source, JAVA, indent=0]
446+
----
447+
include::{sourcedir}/org/hibernate/validator/referenceguide/chapter12/el/ElFeaturesTest.java[tags=constraints]
448+
----
449+
450+
To change the Expression Language feature level for custom violations, use the following:
451+
452+
[source, JAVA, indent=0]
453+
----
454+
include::{sourcedir}/org/hibernate/validator/referenceguide/chapter12/el/ElFeaturesTest.java[tags=customViolations]
455+
----
456+
457+
[CAUTION]
458+
====
459+
Doing this will automatically enable Expression Language for all the custom violations in your application.
460+
461+
It should only be used for compatibility and to ease the migration from older Hibernate Validator versions.
462+
====
463+
464+
These levels can also be defined using the following properties:
465+
466+
* `hibernate.validator.constraint_expression_language_feature_level`
467+
* `hibernate.validator.custom_violation_expression_language_feature_level`
468+
469+
Accepted values for these properties are: `none`, `variables`, `bean-properties` and `bean-methods`.
470+
422471
[[non-el-message-interpolator]]
423472
=== `ParameterMessageInterpolator`
424473

@@ -552,7 +601,7 @@ You can, however, update the parameters between invocations of
552601
* set an arbitrary dynamic payload - see <<section-dynamic-payload>>
553602

554603
By default, Expression Language interpolation is **disabled** for custom violations,
555-
this to avoid arbitrary code execution or sensitive data leak if user input is not properly escaped.
604+
this to avoid arbitrary code execution or sensitive data leak if message templates are built from improperly escaped user input.
556605

557606
It is possible to enable Expression Language for a given custom violation by using `enableExpressionLanguage()` as shown in the example below:
558607

@@ -563,9 +612,21 @@ include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/elinjectio
563612

564613
In this case, the message template will be interpolated by the Expression Language engine.
565614

615+
By default, only variables interpolation is enabled when enabling Expression Language.
616+
617+
You can enable more features by using `HibernateConstraintViolationBuilder#enableExpressionLanguage(ExpressionLanguageFeatureLevel level)`.
618+
619+
We define several levels of features for Expression Language interpolation:
620+
621+
* `NONE`: Expression Language interpolation is fully disabled - this is the default for custom violations.
622+
* `VARIABLES`: Allow interpolation of the variables injected via `addExpressionVariable()`, resources bundles and usage of the `formatter` object.
623+
* `BEAN_PROPERTIES`: Allow everything `VARIABLES` allows plus the interpolation of bean properties.
624+
* `BEAN_METHODS`: Also allow execution of bean methods. This can lead to serious security issues, including arbitrary code execution if not carefully handled.
625+
566626
[CAUTION]
567627
====
568-
Using `addExpressionVariable()` is the only safe way to inject a variable into an expression.
628+
Using `addExpressionVariable()` is the only safe way to inject a variable into an expression
629+
and it's especially important if you use the `BEAN_PROPERTIES` or `BEAN_METHODS` feature levels.
569630
570631
If you inject user input by simply concatenating the user input in the message,
571632
you will allow potential arbitrary code execution and sensitive data leak:
@@ -596,7 +657,7 @@ bundle. If you have any other use cases, let us know.
596657
====
597658
[source, JAVA, indent=0]
598659
----
599-
include::{engine-sourcedir}/org/hibernate/validator/messageinterpolation/HibernateMessageInterpolatorContext.java[lines=18..26]
660+
include::{engine-sourcedir}/org/hibernate/validator/messageinterpolation/HibernateMessageInterpolatorContext.java[lines=22..58]
600661
----
601662
====
602663

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.hibernate.validator.referenceguide.chapter12.el;
2+
3+
import jakarta.validation.Validation;
4+
import jakarta.validation.ValidatorFactory;
5+
6+
import org.hibernate.validator.HibernateValidator;
7+
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
8+
import org.junit.Test;
9+
10+
public class ElFeaturesTest {
11+
12+
@SuppressWarnings("unused")
13+
@Test
14+
public void testConstraints() throws Exception {
15+
//tag::constraints[]
16+
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
17+
.configure()
18+
.constraintExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES )
19+
.buildValidatorFactory();
20+
//end::constraints[]
21+
}
22+
23+
@SuppressWarnings("unused")
24+
@Test
25+
public void testCustomViolations() throws Exception {
26+
//tag::customViolations[]
27+
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
28+
.configure()
29+
.customViolationExpressionLanguageFeatureLevel( ExpressionLanguageFeatureLevel.VARIABLES )
30+
.buildValidatorFactory();
31+
//end::customViolations[]
32+
}
33+
}

engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Set;
1414

1515
import jakarta.validation.Configuration;
16+
import jakarta.validation.ConstraintValidatorContext;
1617
import jakarta.validation.ConstraintViolation;
1718
import jakarta.validation.TraversableResolver;
1819
import jakarta.validation.constraints.Future;
@@ -24,6 +25,7 @@
2425
import org.hibernate.validator.cfg.ConstraintMapping;
2526
import org.hibernate.validator.constraints.ParameterScriptAssert;
2627
import org.hibernate.validator.constraints.ScriptAssert;
28+
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
2729
import org.hibernate.validator.spi.messageinterpolation.LocaleResolver;
2830
import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;
2931
import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider;
@@ -144,6 +146,28 @@ public interface BaseHibernateValidatorConfiguration<S extends BaseHibernateVali
144146
@Incubating
145147
String LOCALE_RESOLVER_CLASSNAME = "hibernate.validator.locale_resolver";
146148

149+
/**
150+
* Property for configuring the Expression Language feature level for constraints, allowing to define which
151+
* Expression Language features are available for message interpolation.
152+
* <p>
153+
* This property only affects the EL feature level of "static" constraint violation messages. In particular, it
154+
* doesn't affect the default EL feature level for custom violations. Refer to
155+
* {@link #CUSTOM_VIOLATION_EXPRESSION_LANGUAGE_FEATURE_LEVEL} to configure that.
156+
*
157+
* @since 6.2
158+
*/
159+
@Incubating
160+
String CONSTRAINT_EXPRESSION_LANGUAGE_FEATURE_LEVEL = "hibernate.validator.constraint_expression_language_feature_level";
161+
162+
/**
163+
* Property for configuring the Expression Language feature level for custom violations, allowing to define which
164+
* Expression Language features are available for message interpolation.
165+
*
166+
* @since 6.2
167+
*/
168+
@Incubating
169+
String CUSTOM_VIOLATION_EXPRESSION_LANGUAGE_FEATURE_LEVEL = "hibernate.validator.custom_violation_expression_language_feature_level";
170+
147171
/**
148172
* <p>
149173
* Returns the {@link ResourceBundleLocator} used by the
@@ -427,4 +451,33 @@ default S locales(Locale... locales) {
427451

428452
@Incubating
429453
S beanMetaDataClassNormalizer(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer);
454+
455+
/**
456+
* Allows setting the Expression Language feature level for message interpolation of constraint messages.
457+
* <p>
458+
* This is the feature level used for messages hardcoded inside the constraint declaration.
459+
* <p>
460+
* If you are creating custom constraint violations, Expression Language support needs to be explicitly enabled and
461+
* use the safest feature level by default if enabled.
462+
*
463+
* @param expressionLanguageFeatureLevel the {@link ExpressionLanguageFeatureLevel} to be used
464+
* @return {@code this} following the chaining method pattern
465+
*
466+
* @since 6.2
467+
*/
468+
@Incubating
469+
S constraintExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
470+
471+
/**
472+
* Allows setting the Expression Language feature level for message interpolation of custom violation messages.
473+
* <p>
474+
* This is the feature level used for messages of custom violations created by the {@link ConstraintValidatorContext}.
475+
*
476+
* @param expressionLanguageFeatureLevel the {@link ExpressionLanguageFeatureLevel} to be used
477+
* @return {@code this} following the chaining method pattern
478+
*
479+
* @since 6.2
480+
*/
481+
@Incubating
482+
S customViolationExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel);
430483
}

engine/src/main/java/org/hibernate/validator/constraintvalidation/HibernateConstraintViolationBuilder.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,34 @@
1010
import jakarta.validation.ConstraintValidatorContext.ConstraintViolationBuilder;
1111

1212
import org.hibernate.validator.Incubating;
13+
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
1314

1415
public interface HibernateConstraintViolationBuilder extends ConstraintViolationBuilder {
1516

17+
/**
18+
* Enable Expression Language with the default Expression Language feature level for the constraint violation
19+
* created by this builder if the chosen {@code MessageInterpolator} supports it.
20+
* <p>
21+
* If you enable this, you need to make sure your message template does not contain any unescaped user input (such as
22+
* the validated value): use {@code addExpressionVariable()} to inject properly escaped variables into the template.
23+
*
24+
* @since 6.2
25+
*/
26+
@Incubating
27+
default HibernateConstraintViolationBuilder enableExpressionLanguage() {
28+
return enableExpressionLanguage( ExpressionLanguageFeatureLevel.DEFAULT );
29+
};
30+
1631
/**
1732
* Enable Expression Language for the constraint violation created by this builder if the chosen
1833
* {@code MessageInterpolator} supports it.
1934
* <p>
20-
* If enabling this, you need to make sure your message template does not contain any unescaped user input (such as
35+
* If you enable this, you need to make sure your message template does not contain any unescaped user input (such as
2136
* the validated value): use {@code addExpressionVariable()} to inject properly escaped variables into the template.
2237
*
38+
* @param level The Expression Language features level supported.
2339
* @since 6.2
2440
*/
2541
@Incubating
26-
HibernateConstraintViolationBuilder enableExpressionLanguage();
42+
HibernateConstraintViolationBuilder enableExpressionLanguage(ExpressionLanguageFeatureLevel level);
2743
}

engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.hibernate.validator.internal.util.stereotypes.Lazy;
5858
import org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters;
5959
import org.hibernate.validator.internal.xml.config.ValidationXmlParser;
60+
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
6061
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
6162
import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;
6263
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
@@ -129,6 +130,8 @@ public abstract class AbstractConfigurationImpl<T extends BaseHibernateValidator
129130
private Locale defaultLocale = Locale.getDefault();
130131
private LocaleResolver localeResolver;
131132
private BeanMetaDataClassNormalizer beanMetaDataClassNormalizer;
133+
private ExpressionLanguageFeatureLevel constraintExpressionLanguageFeatureLevel;
134+
private ExpressionLanguageFeatureLevel customViolationExpressionLanguageFeatureLevel;
132135

133136
protected AbstractConfigurationImpl(BootstrapState state) {
134137
this();
@@ -627,6 +630,35 @@ public BeanMetaDataClassNormalizer getBeanMetaDataClassNormalizer() {
627630
return beanMetaDataClassNormalizer;
628631
}
629632

633+
@Override
634+
public T constraintExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel) {
635+
if ( LOG.isDebugEnabled() ) {
636+
if ( expressionLanguageFeatureLevel != null ) {
637+
LOG.debug( "Setting ExpressionLanguageFeatureLevel for constraints to " + expressionLanguageFeatureLevel.name() );
638+
}
639+
}
640+
this.constraintExpressionLanguageFeatureLevel = expressionLanguageFeatureLevel;
641+
return thisAsT();
642+
}
643+
644+
public ExpressionLanguageFeatureLevel getConstraintExpressionLanguageFeatureLevel() {
645+
return constraintExpressionLanguageFeatureLevel;
646+
}
647+
648+
@Override
649+
public T customViolationExpressionLanguageFeatureLevel(ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel) {
650+
if ( LOG.isDebugEnabled() ) {
651+
if ( expressionLanguageFeatureLevel != null ) {
652+
LOG.debug( "Setting ExpressionLanguageFeatureLevel for custom violations to " + expressionLanguageFeatureLevel.name() );
653+
}
654+
}
655+
this.customViolationExpressionLanguageFeatureLevel = expressionLanguageFeatureLevel;
656+
return thisAsT();
657+
}
658+
659+
public ExpressionLanguageFeatureLevel getCustomViolationExpressionLanguageFeatureLevel() {
660+
return customViolationExpressionLanguageFeatureLevel;
661+
}
630662

631663
public final Set<DefaultConstraintMapping> getProgrammaticMappings() {
632664
return programmaticMappings;

engine/src/main/java/org/hibernate/validator/internal/engine/MessageInterpolatorContext.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.hibernate.validator.internal.util.logging.Log;
1818
import org.hibernate.validator.internal.util.logging.LoggerFactory;
1919
import org.hibernate.validator.internal.util.stereotypes.Immutable;
20+
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
2021
import org.hibernate.validator.messageinterpolation.HibernateMessageInterpolatorContext;
2122

2223
/**
@@ -39,22 +40,25 @@ public class MessageInterpolatorContext implements HibernateMessageInterpolatorC
3940
private final Map<String, Object> messageParameters;
4041
@Immutable
4142
private final Map<String, Object> expressionVariables;
42-
private final boolean expressionLanguageEnabled;
43+
private final ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel;
44+
private final boolean customViolation;
4345

4446
public MessageInterpolatorContext(ConstraintDescriptor<?> constraintDescriptor,
4547
Object validatedValue,
4648
Class<?> rootBeanType,
4749
Path propertyPath,
4850
Map<String, Object> messageParameters,
4951
Map<String, Object> expressionVariables,
50-
boolean expressionLanguageEnabled) {
52+
ExpressionLanguageFeatureLevel expressionLanguageFeatureLevel,
53+
boolean customViolation) {
5154
this.constraintDescriptor = constraintDescriptor;
5255
this.validatedValue = validatedValue;
5356
this.rootBeanType = rootBeanType;
5457
this.propertyPath = propertyPath;
5558
this.messageParameters = toImmutableMap( messageParameters );
5659
this.expressionVariables = toImmutableMap( expressionVariables );
57-
this.expressionLanguageEnabled = expressionLanguageEnabled;
60+
this.expressionLanguageFeatureLevel = expressionLanguageFeatureLevel;
61+
this.customViolation = customViolation;
5862
}
5963

6064
@Override
@@ -78,8 +82,12 @@ public Map<String, Object> getMessageParameters() {
7882
}
7983

8084
@Override
81-
public boolean isExpressionLanguageEnabled() {
82-
return expressionLanguageEnabled;
85+
public ExpressionLanguageFeatureLevel getExpressionLanguageFeatureLevel() {
86+
return expressionLanguageFeatureLevel;
87+
}
88+
89+
public boolean isCustomViolation() {
90+
return customViolation;
8391
}
8492

8593
@Override
@@ -143,7 +151,8 @@ public String toString() {
143151
sb.append( ", propertyPath=" ).append( propertyPath );
144152
sb.append( ", messageParameters=" ).append( messageParameters );
145153
sb.append( ", expressionVariables=" ).append( expressionVariables );
146-
sb.append( ", expressionLanguageEnabled=" ).append( expressionLanguageEnabled );
154+
sb.append( ", expressionLanguageFeatureLevel=" ).append( expressionLanguageFeatureLevel );
155+
sb.append( ", customViolation=" ).append( customViolation );
147156
sb.append( '}' );
148157
return sb.toString();
149158
}

0 commit comments

Comments
 (0)