Skip to content

Commit b96cf2c

Browse files
committed
WW-5429 Log parameter annotation issues at ERROR level when in DevMode
1 parent 444e4d4 commit b96cf2c

File tree

5 files changed

+112
-52
lines changed

5 files changed

+112
-52
lines changed

core/src/main/java/com/opensymphony/xwork2/interceptor/ValidationAware.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept
2727
* in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs.
2828
*
29-
* @author plightbo
29+
* @author plightbo
3030
*/
3131
public interface ValidationAware {
3232

@@ -119,7 +119,9 @@ public interface ValidationAware {
119119
*
120120
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
121121
*/
122-
boolean hasErrors();
122+
default boolean hasErrors() {
123+
return hasActionErrors() || hasFieldErrors();
124+
}
123125

124126
/**
125127
* Check whether there are any field errors associated with this action.

core/src/main/java/com/opensymphony/xwork2/ognl/ErrorMessageBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ private ErrorMessageBuilder() {
3333
}
3434

3535
public ErrorMessageBuilder errorSettingExpressionWithValue(String expr, Object value) {
36-
appenExpression(expr);
36+
appendExpression(expr);
3737
if (value instanceof Object[]) {
3838
appendValueAsArray((Object[]) value, message);
3939
} else {
@@ -42,7 +42,7 @@ public ErrorMessageBuilder errorSettingExpressionWithValue(String expr, Object v
4242
return this;
4343
}
4444

45-
private void appenExpression(String expr) {
45+
private void appendExpression(String expr) {
4646
message.append("Error setting expression '");
4747
message.append(expr);
4848
message.append("' with value ");
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package com.opensymphony.xwork2.util;
20+
21+
import com.opensymphony.xwork2.TextProvider;
22+
import com.opensymphony.xwork2.interceptor.ValidationAware;
23+
import org.apache.logging.log4j.Logger;
24+
25+
/**
26+
* @since 6.5.0
27+
*/
28+
public class DebugUtils {
29+
30+
public static void notifyDeveloperOfError(Logger log, Object action, String message) {
31+
if (action instanceof TextProvider) {
32+
TextProvider tp = (TextProvider) action;
33+
message = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{message});
34+
}
35+
log.error(message);
36+
if (action instanceof ValidationAware) {
37+
ValidationAware validationAware = (ValidationAware) action;
38+
validationAware.addActionError(message);
39+
}
40+
}
41+
42+
}

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

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020

2121
import com.opensymphony.xwork2.ActionContext;
2222
import com.opensymphony.xwork2.ActionInvocation;
23-
import com.opensymphony.xwork2.TextProvider;
2423
import com.opensymphony.xwork2.inject.Inject;
2524
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
26-
import com.opensymphony.xwork2.interceptor.ValidationAware;
2725
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
2826
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
2927
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
@@ -56,7 +54,6 @@
5654
import java.lang.reflect.ParameterizedType;
5755
import java.lang.reflect.Type;
5856
import java.util.Arrays;
59-
import java.util.Collection;
6057
import java.util.Comparator;
6158
import java.util.HashSet;
6259
import java.util.Map;
@@ -67,6 +64,8 @@
6764

6865
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS;
6966
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS_STR;
67+
import static com.opensymphony.xwork2.util.DebugUtils.notifyDeveloperOfError;
68+
import static java.lang.String.format;
7069
import static java.util.Collections.unmodifiableSet;
7170
import static java.util.stream.Collectors.joining;
7271
import static org.apache.commons.lang3.StringUtils.indexOfAny;
@@ -317,19 +316,8 @@ protected void applyParametersOnStack(ValueStack stack, Map<String, Parameter> p
317316
}
318317

319318
protected void notifyDeveloperParameterException(Object action, String property, String message) {
320-
String logMsg = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
321-
if (action instanceof TextProvider) {
322-
TextProvider tp = (TextProvider) action;
323-
logMsg = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{logMsg});
324-
}
325-
LOG.error(logMsg);
326-
327-
if (action instanceof ValidationAware) {
328-
ValidationAware validationAware = (ValidationAware) action;
329-
Collection<String> messages = validationAware.getActionMessages();
330-
messages.add(message);
331-
validationAware.setActionMessages(messages);
332-
}
319+
String logMsg = format("Unexpected Exception caught setting '%s' on '%s: %s", property, action.getClass(), message);
320+
notifyDeveloperOfError(LOG, action, logMsg);
333321
}
334322

335323
/**
@@ -388,23 +376,37 @@ protected boolean hasValidAnnotatedMember(String rootProperty, Object action, lo
388376
return hasValidAnnotatedField(action, rootProperty, paramDepth);
389377
}
390378

391-
if (hasValidAnnotatedPropertyDescriptor(propDescOpt.get(), paramDepth)) {
379+
if (hasValidAnnotatedPropertyDescriptor(action, propDescOpt.get(), paramDepth)) {
392380
return true;
393381
}
394382

395383
return hasValidAnnotatedField(action, rootProperty, paramDepth);
396384
}
397385

386+
/**
387+
* @deprecated since 6.5.0, use {@link #hasValidAnnotatedPropertyDescriptor(Object, PropertyDescriptor, long)}
388+
* instead.
389+
*/
390+
@Deprecated
398391
protected boolean hasValidAnnotatedPropertyDescriptor(PropertyDescriptor propDesc, long paramDepth) {
392+
return hasValidAnnotatedPropertyDescriptor(null, propDesc, paramDepth);
393+
}
394+
395+
protected boolean hasValidAnnotatedPropertyDescriptor(Object action, PropertyDescriptor propDesc, long paramDepth) {
399396
Method relevantMethod = paramDepth == 0 ? propDesc.getWriteMethod() : propDesc.getReadMethod();
400397
if (relevantMethod == null) {
401398
return false;
402399
}
403400
if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
404-
LOG.debug(
405-
"Parameter injection for method [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
401+
String logMessage = format(
402+
"Parameter injection for method [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
406403
relevantMethod.getName(),
407404
relevantMethod.getDeclaringClass().getName());
405+
if (devMode) {
406+
notifyDeveloperOfError(LOG, action, logMessage);
407+
} else {
408+
LOG.debug(logMessage);
409+
}
408410
return false;
409411
}
410412
if (paramDepth >= 1) {
@@ -455,10 +457,15 @@ protected boolean hasValidAnnotatedField(Object action, String fieldName, long p
455457
return false;
456458
}
457459
if (getPermittedInjectionDepth(field) < paramDepth) {
458-
LOG.debug(
459-
"Parameter injection for field [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
460+
String logMessage = format(
461+
"Parameter injection for field [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
460462
fieldName,
461463
action.getClass().getName());
464+
if (devMode) {
465+
notifyDeveloperOfError(LOG, action, logMessage);
466+
} else {
467+
LOG.debug(logMessage);
468+
}
462469
return false;
463470
}
464471
if (paramDepth >= 1) {
@@ -533,7 +540,7 @@ protected String getParameterLogMap(HttpParameters parameters) {
533540
return "NONE";
534541
}
535542
return parameters.entrySet().stream()
536-
.map(entry -> String.format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
543+
.map(entry -> format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
537544
.collect(joining());
538545
}
539546

core/src/test/java/org/apache/struts2/interceptor/parameter/ParametersInterceptorTest.java

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,15 +116,17 @@ public void testInsecureParameters() throws Exception {
116116
pi.setParameters(action, vs, HttpParameters.create(params).build());
117117

118118
// then
119-
assertEquals(3, action.getActionMessages().size());
119+
assertEquals(3, action.getActionErrors().size());
120120

121-
String msg1 = action.getActionMessage(0);
122-
String msg2 = action.getActionMessage(1);
123-
String msg3 = action.getActionMessage(2);
121+
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
124122

125-
assertEquals("Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#[email protected]@getRequest(),#[email protected]@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
126-
assertEquals("Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
127-
assertEquals("Error setting expression 'top['name'](0)' with value 'true'", msg3);
123+
String msg1 = actionErrors.get(0);
124+
String msg2 = actionErrors.get(1);
125+
String msg3 = actionErrors.get(2);
126+
127+
assertEquals("Unexpected Exception caught setting 'expression' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#[email protected]@getRequest(),#[email protected]@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
128+
assertEquals("Unexpected Exception caught setting 'name' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
129+
assertEquals("Unexpected Exception caught setting 'top['name'](0)' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'top['name'](0)' with value 'true'", msg3);
128130
assertNull(action.getName());
129131
}
130132

@@ -201,15 +203,16 @@ protected boolean isExcluded(String paramName) {
201203
pi.setParameters(action, vs, HttpParameters.create(params).build());
202204

203205
// then
204-
assertEquals(3, action.getActionMessages().size());
206+
assertEquals(3, action.getActionErrors().size());
205207

206-
String msg1 = action.getActionMessage(0);
207-
String msg2 = action.getActionMessage(1);
208-
String msg3 = action.getActionMessage(2);
208+
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
209+
String msg1 = actionErrors.get(0);
210+
String msg2 = actionErrors.get(1);
211+
String msg3 = actionErrors.get(2);
209212

210-
assertEquals("Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
211-
assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
212-
assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
213+
assertEquals("Unexpected Exception caught setting 'class.classLoader.defaultAssertionStatus' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
214+
assertEquals("Unexpected Exception caught setting 'class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
215+
assertEquals("Unexpected Exception caught setting 'model.class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
213216

214217
assertFalse(excluded.get(pollution1));
215218
assertFalse(excluded.get(pollution2));
@@ -582,8 +585,8 @@ public void testNonexistentParametersGetLoggedInDevMode() throws Exception {
582585
container.inject(config.getInterceptors().get(0).getInterceptor());
583586
ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap());
584587
proxy.execute();
585-
final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
586-
assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
588+
final String actionError = "" + ((SimpleAction) proxy.getAction()).getActionErrors().toArray()[0];
589+
assertTrue(actionError.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
587590
}
588591

589592
public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception {
@@ -1014,59 +1017,65 @@ protected void setUp() throws Exception {
10141017
class ValidateAction implements ValidationAware {
10151018

10161019
private final List<String> messages = new LinkedList<>();
1020+
private final List<String> errors = new LinkedList<>();
10171021
private String name;
10181022

1023+
@Override
10191024
public void setActionErrors(Collection<String> errorMessages) {
10201025
}
10211026

1027+
@Override
10221028
public Collection<String> getActionErrors() {
1023-
return null;
1029+
return errors;
10241030
}
10251031

1032+
@Override
10261033
public void setActionMessages(Collection<String> messages) {
10271034
}
10281035

1036+
@Override
10291037
public Collection<String> getActionMessages() {
10301038
return messages;
10311039
}
10321040

1041+
@Override
10331042
public void setFieldErrors(Map<String, List<String>> errorMap) {
10341043
}
10351044

1045+
@Override
10361046
public Map<String, List<String>> getFieldErrors() {
10371047
return null;
10381048
}
10391049

1050+
@Override
10401051
public void addActionError(String anErrorMessage) {
1052+
errors.add(anErrorMessage);
10411053
}
10421054

1055+
@Override
10431056
public void addActionMessage(String aMessage) {
10441057
messages.add(aMessage);
10451058
}
10461059

1060+
@Override
10471061
public void addFieldError(String fieldName, String errorMessage) {
10481062
}
10491063

1064+
@Override
10501065
public boolean hasActionErrors() {
1051-
return false;
1066+
return !errors.isEmpty();
10521067
}
10531068

1069+
@Override
10541070
public boolean hasActionMessages() {
10551071
return !messages.isEmpty();
10561072
}
10571073

1058-
public boolean hasErrors() {
1059-
return false;
1060-
}
1061-
1074+
@Override
10621075
public boolean hasFieldErrors() {
10631076
return false;
10641077
}
10651078

1066-
public String getActionMessage(int index) {
1067-
return messages.get(index);
1068-
}
1069-
10701079
public String getName() {
10711080
return name;
10721081
}

0 commit comments

Comments
 (0)