Skip to content

Commit 762a0e0

Browse files
authored
Merge pull request #1072 from apache/fix/WW-5468-modeldriven-2
WW-5468 Exempt ModelDriven Actions from @StrutsParameter requirement
2 parents ddc2944 + f6c17e9 commit 762a0e0

File tree

17 files changed

+292
-260
lines changed

17 files changed

+292
-260
lines changed

apps/showcase/src/main/java/org/apache/struts2/showcase/modelDriven/ModelDrivenAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public String execute() throws Exception {
4242
}
4343

4444
@Override
45-
public Object getModel() {
45+
public Gangster getModel() {
4646
return new Gangster();
4747
}
4848
}

core/src/main/java/com/opensymphony/xwork2/ModelDriven.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package com.opensymphony.xwork2;
2020

21+
import org.apache.struts2.interceptor.parameter.StrutsParameter;
22+
2123
/**
2224
* ModelDriven Actions provide a model object to be pushed onto the ValueStack
2325
* in addition to the Action itself, allowing a FormBean type approach like Struts.
@@ -28,9 +30,13 @@ public interface ModelDriven<T> {
2830

2931
/**
3032
* Gets the model to be pushed onto the ValueStack instead of the Action itself.
33+
* <p>
34+
* Please be aware that all setters and getters of every depth on the object returned by this method are available
35+
* for user parameter injection!
3136
*
3237
* @return the model
3338
*/
39+
@StrutsParameter(depth = Integer.MAX_VALUE)
3440
T getModel();
3541

3642
}

core/src/main/java/org/apache/struts2/interceptor/parameter/ParametersInterceptor.java

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.opensymphony.xwork2.ActionContext;
2222
import com.opensymphony.xwork2.ActionInvocation;
23+
import com.opensymphony.xwork2.ModelDriven;
2324
import com.opensymphony.xwork2.inject.Inject;
2425
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
2526
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
@@ -258,19 +259,19 @@ protected void batchApplyReflectionContextState(Map<String, Object> context, boo
258259

259260
protected ValueStack toNewStack(ValueStack stack) {
260261
ValueStack newStack = valueStackFactory.createValueStack(stack);
261-
if (newStack instanceof ClearableValueStack) {
262-
((ClearableValueStack) newStack).clearContextValues();
262+
if (newStack instanceof ClearableValueStack clearable) {
263+
clearable.clearContextValues();
263264
newStack.getActionContext().withLocale(stack.getActionContext().getLocale()).withValueStack(stack);
264265
}
265266
return newStack;
266267
}
267268

268269
protected void applyMemberAccessProperties(ValueStack stack) {
269-
if (!(stack instanceof MemberAccessValueStack)) {
270+
if (!(stack instanceof MemberAccessValueStack accessValueStack)) {
270271
return;
271272
}
272-
((MemberAccessValueStack) stack).useAcceptProperties(acceptedPatterns.getAcceptedPatterns());
273-
((MemberAccessValueStack) stack).useExcludeProperties(excludedPatterns.getExcludedPatterns());
273+
accessValueStack.useAcceptProperties(acceptedPatterns.getAcceptedPatterns());
274+
accessValueStack.useExcludeProperties(excludedPatterns.getExcludedPatterns());
274275
}
275276

276277
protected Map<String, Parameter> toAcceptableParameters(HttpParameters parameters, Object action) {
@@ -332,7 +333,7 @@ protected boolean isAcceptableParameter(String name, Object action) {
332333
}
333334

334335
protected boolean isAcceptableParameterNameAware(String name, Object action) {
335-
return !(action instanceof ParameterNameAware) || ((ParameterNameAware) action).acceptableParameterName(name);
336+
return !(action instanceof ParameterNameAware nameAware) || nameAware.acceptableParameterName(name);
336337
}
337338

338339
/**
@@ -348,7 +349,15 @@ protected boolean isParameterAnnotatedAndAllowlist(String name, Object action) {
348349
}
349350

350351
long paramDepth = name.codePoints().mapToObj(c -> (char) c).filter(NESTING_CHARS::contains).count();
352+
353+
if (action instanceof ModelDriven<?> && !ActionContext.getContext().getValueStack().peek().equals(action)) {
354+
LOG.debug("Model driven Action detected, exempting from @StrutsParameter annotation requirement and OGNL allowlisting model type");
355+
// (Exempted by annotation on com.opensymphony.xwork2.ModelDriven#getModel)
356+
return hasValidAnnotatedMember("model", action, paramDepth + 1);
357+
}
358+
351359
if (requireAnnotationsTransitionMode && paramDepth == 0) {
360+
LOG.debug("Annotation transition mode enabled, exempting non-nested parameter [{}] from @StrutsParameter annotation requirement", name);
352361
return true;
353362
}
354363

@@ -365,6 +374,8 @@ protected boolean isParameterAnnotatedAndAllowlist(String name, Object action) {
365374
* save computation by checking this last.
366375
*/
367376
protected boolean hasValidAnnotatedMember(String rootProperty, Object action, long paramDepth) {
377+
LOG.debug("Checking Action [{}] for a matching, correctly annotated member for property [{}]",
378+
action.getClass().getSimpleName(), rootProperty);
368379
BeanInfo beanInfo = getBeanInfo(action);
369380
if (beanInfo == null) {
370381
return hasValidAnnotatedField(action, rootProperty, paramDepth);
@@ -390,7 +401,7 @@ protected boolean hasValidAnnotatedPropertyDescriptor(Object action, PropertyDes
390401
}
391402
if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
392403
String logMessage = format(
393-
"Parameter injection for method [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
404+
"Parameter injection for method [%s] on Action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
394405
relevantMethod.getName(),
395406
relevantMethod.getDeclaringClass().getName());
396407
if (devMode) {
@@ -400,8 +411,10 @@ protected boolean hasValidAnnotatedPropertyDescriptor(Object action, PropertyDes
400411
}
401412
return false;
402413
}
414+
LOG.debug("Success: Matching annotated method [{}] found for property [{}] of depth [{}] on Action [{}]",
415+
relevantMethod.getName(), propDesc.getName(), paramDepth, action.getClass().getSimpleName());
403416
if (paramDepth >= 1) {
404-
allowlistClass(relevantMethod.getReturnType());
417+
allowlistClass(propDesc.getPropertyType());
405418
}
406419
if (paramDepth >= 2) {
407420
allowlistReturnTypeIfParameterized(relevantMethod);
@@ -438,19 +451,23 @@ protected void allowlistClass(Class<?> clazz) {
438451
}
439452

440453
protected boolean hasValidAnnotatedField(Object action, String fieldName, long paramDepth) {
454+
LOG.debug("No matching annotated method found for property [{}] of depth [{}] on Action [{}], now also checking for public field",
455+
fieldName, paramDepth, action.getClass().getSimpleName());
441456
Field field;
442457
try {
443458
field = action.getClass().getDeclaredField(fieldName);
444459
} catch (NoSuchFieldException e) {
460+
LOG.debug("Matching field for property [{}] not found on Action [{}]", fieldName, action.getClass().getSimpleName());
445461
return false;
446462
}
447463
if (!Modifier.isPublic(field.getModifiers())) {
464+
LOG.debug("Matching field [{}] is not public on Action [{}]", field.getName(), action.getClass().getSimpleName());
448465
return false;
449466
}
450467
if (getPermittedInjectionDepth(field) < paramDepth) {
451468
String logMessage = format(
452-
"Parameter injection for field [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
453-
fieldName,
469+
"Parameter injection for field [%s] on Action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
470+
field.getName(),
454471
action.getClass().getName());
455472
if (devMode) {
456473
notifyDeveloperOfError(LOG, action, logMessage);
@@ -459,6 +476,8 @@ protected boolean hasValidAnnotatedField(Object action, String fieldName, long p
459476
}
460477
return false;
461478
}
479+
LOG.debug("Success: Matching annotated public field [{}] found for property of depth [{}] on Action [{}]",
480+
field.getName(), paramDepth, action.getClass().getSimpleName());
462481
if (paramDepth >= 1) {
463482
allowlistClass(field.getType());
464483
}
@@ -512,7 +531,7 @@ protected boolean isAcceptableParameterValue(Parameter param, Object action) {
512531
}
513532

514533
protected boolean isAcceptableParameterValueAware(Parameter param, Object action) {
515-
return !(action instanceof ParameterValueAware) || ((ParameterValueAware) action).acceptableParameterValue(param.getValue());
534+
return !(action instanceof ParameterValueAware valueAware) || valueAware.acceptableParameterValue(param.getValue());
516535
}
517536

518537
/**
@@ -606,7 +625,7 @@ protected boolean isAccepted(String paramName) {
606625
if (!result.isAccepted()) {
607626
if (devMode) {
608627
LOG.warn("Parameter [{}] didn't match accepted pattern [{}]! See Accepted / Excluded patterns at\n" +
609-
"https://struts.apache.org/security/#accepted--excluded-patterns",
628+
"https://struts.apache.org/security/#accepted--excluded-patterns",
610629
paramName, result.getAcceptedPattern());
611630
} else {
612631
LOG.debug("Parameter [{}] didn't match accepted pattern [{}]!", paramName, result.getAcceptedPattern());
@@ -621,8 +640,8 @@ protected boolean isExcluded(String paramName) {
621640
if (result.isExcluded()) {
622641
if (devMode) {
623642
LOG.warn("Parameter [{}] matches excluded pattern [{}]! See Accepted / Excluded patterns at\n" +
624-
"https://struts.apache.org/security/#accepted--excluded-patterns",
625-
paramName, result.getExcludedPattern());
643+
"https://struts.apache.org/security/#accepted--excluded-patterns",
644+
paramName, result.getExcludedPattern());
626645
} else {
627646
LOG.debug("Parameter [{}] matches excluded pattern [{}]!", paramName, result.getExcludedPattern());
628647
}
@@ -640,8 +659,8 @@ protected boolean isParamValueExcluded(String value) {
640659
if (excludedValuePattern.matcher(value).matches()) {
641660
if (devMode) {
642661
LOG.warn("Parameter value [{}] matches excluded pattern [{}]! See Accepting/Excluding parameter values at\n" +
643-
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
644-
value, excludedValuePatterns);
662+
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
663+
value, excludedValuePatterns);
645664
} else {
646665
LOG.debug("Parameter value [{}] matches excluded pattern [{}]", value, excludedValuePattern);
647666
}
@@ -663,8 +682,8 @@ protected boolean isParamValueAccepted(String value) {
663682
}
664683
if (devMode) {
665684
LOG.warn("Parameter value [{}] didn't match accepted pattern [{}]! See Accepting/Excluding parameter values at\n" +
666-
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
667-
value, acceptedValuePatterns);
685+
"https://struts.apache.org/core-developers/parameters-interceptor#excluding-parameter-values",
686+
value, acceptedValuePatterns);
668687
} else {
669688
LOG.debug("Parameter value [{}] was not accepted!", value);
670689
}
@@ -734,7 +753,7 @@ public void setAcceptedValuePatterns(String commaDelimitedPatterns) {
734753
LOG.debug("Sets accepted value patterns to [{}], note this may impact the safety of your application!", patterns);
735754
} else {
736755
LOG.warn("Replacing accepted patterns [{}] with [{}], be aware that this may impact safety of your application!",
737-
acceptedValuePatterns, patterns);
756+
acceptedValuePatterns, patterns);
738757
}
739758
acceptedValuePatterns = new HashSet<>(patterns.size());
740759
try {
@@ -759,7 +778,7 @@ public void setExcludedValuePatterns(String commaDelimitedPatterns) {
759778
LOG.debug("Setting excluded value patterns to [{}]", patterns);
760779
} else {
761780
LOG.warn("Replacing excluded value patterns [{}] with [{}], be aware that this may impact safety of your application!",
762-
excludedValuePatterns, patterns);
781+
excludedValuePatterns, patterns);
763782
}
764783
excludedValuePatterns = new HashSet<>(patterns.size());
765784
try {

core/src/test/java/com/opensymphony/xwork2/ModelDrivenAction.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ public String getFoo() {
4444
/**
4545
* @return the model to be pushed onto the ValueStack after the Action itself
4646
*/
47-
@StrutsParameter(depth = 2)
4847
@Override
49-
public Object getModel() {
48+
public TestBean getModel() {
5049
return model;
5150
}
5251
}

core/src/test/java/com/opensymphony/xwork2/ModelDrivenAnnotationAction.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ public String getFoo() {
4444
/**
4545
* @return the model to be pushed onto the ValueStack after the Action itself
4646
*/
47-
@StrutsParameter(depth = 2)
4847
@Override
49-
public Object getModel() {
48+
public AnnotatedTestBean getModel() {
5049
return model;
5150
}
5251
}

core/src/test/java/com/opensymphony/xwork2/interceptor/ModelDrivenInterceptorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ protected void tearDown() throws Exception {
178178
}
179179

180180

181-
public class ModelDrivenAction extends ActionSupport implements ModelDriven {
181+
public class ModelDrivenAction extends ActionSupport implements ModelDriven<Object> {
182182

183183
@Override
184184
public Object getModel() {

core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAction2.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package com.opensymphony.xwork2.test;
2020

2121
import com.opensymphony.xwork2.ModelDrivenAction;
22-
import org.apache.struts2.interceptor.parameter.StrutsParameter;
2322

2423

2524
/**
@@ -35,9 +34,8 @@ public class ModelDrivenAction2 extends ModelDrivenAction {
3534
/**
3635
* @return the model to be pushed onto the ValueStack after the Action itself
3736
*/
38-
@StrutsParameter(depth = 3)
3937
@Override
40-
public Object getModel() {
38+
public TestBean2 getModel() {
4139
return model;
4240
}
4341
}

core/src/test/java/com/opensymphony/xwork2/test/ModelDrivenAnnotationAction2.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package com.opensymphony.xwork2.test;
2020

2121
import com.opensymphony.xwork2.ModelDrivenAnnotationAction;
22-
import org.apache.struts2.interceptor.parameter.StrutsParameter;
2322

2423

2524
/**
@@ -36,9 +35,8 @@ public class ModelDrivenAnnotationAction2 extends ModelDrivenAnnotationAction {
3635
/**
3736
* @return the model to be pushed onto the ValueStack after the Action itself
3837
*/
39-
@StrutsParameter(depth = 3)
4038
@Override
41-
public Object getModel() {
39+
public AnnotationTestBean2 getModel() {
4240
return model;
4341
}
4442
}

core/src/test/java/com/opensymphony/xwork2/test/subtest/NullModelDrivenAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.opensymphony.xwork2.test.subtest;
2020

2121
import com.opensymphony.xwork2.ModelDrivenAction;
22+
import com.opensymphony.xwork2.TestBean;
2223

2324
/**
2425
* Extends ModelDrivenAction to return a null model.
@@ -31,7 +32,7 @@ public class NullModelDrivenAction extends ModelDrivenAction {
3132
* @return the model to be pushed onto the ValueStack instead of the Action itself
3233
*/
3334
@Override
34-
public Object getModel() {
35+
public TestBean getModel() {
3536
return null;
3637
}
3738
}

core/src/test/java/com/opensymphony/xwork2/validator/VisitorValidatorModelAction.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
package com.opensymphony.xwork2.validator;
2020

2121
import com.opensymphony.xwork2.ModelDriven;
22-
import org.apache.struts2.interceptor.parameter.StrutsParameter;
22+
import com.opensymphony.xwork2.TestBean;
2323

2424

2525
/**
@@ -33,9 +33,8 @@ public class VisitorValidatorModelAction extends VisitorValidatorTestAction impl
3333
/**
3434
* @return the model to be pushed onto the ValueStack instead of the Action itself
3535
*/
36-
@StrutsParameter(depth = 2)
3736
@Override
38-
public Object getModel() {
37+
public TestBean getModel() {
3938
return getBean();
4039
}
4140
}

0 commit comments

Comments
 (0)