Skip to content

Commit b65005c

Browse files
authored
Merge pull request #980 from apache/7.0.x/merge-master-2024-07-08
Merge master to 7.0.x, 2024-07-08
2 parents 6cebeac + e2d5cc2 commit b65005c

File tree

18 files changed

+500
-167
lines changed

18 files changed

+500
-167
lines changed

core/src/main/java/com/opensymphony/xwork2/config/providers/EnvsValueSubstitutor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public EnvsValueSubstitutor() {
4646
public String substitute(String value) {
4747
LOG.debug("Substituting value {} with proper System variable or environment variable", value);
4848

49-
String substituted = sysStrSubstitutor.replace(value);
50-
return envStrSubstitutor.replace(substituted);
49+
String substituted = envStrSubstitutor.replace(value);
50+
return sysStrSubstitutor.replace(substituted);
5151
}
5252
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
/**
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.
28-
*
29-
* @author plightbo
3028
*/
3129
public interface ValidationAware {
3230

@@ -119,7 +117,9 @@ public interface ValidationAware {
119117
*
120118
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
121119
*/
122-
boolean hasErrors();
120+
default boolean hasErrors() {
121+
return hasActionErrors() || hasFieldErrors();
122+
}
123123

124124
/**
125125
* 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 ");

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

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import java.util.HashMap;
4848
import java.util.Map;
4949
import java.util.Set;
50-
import java.util.concurrent.atomic.AtomicBoolean;
5150
import java.util.regex.Pattern;
5251

5352
import static com.opensymphony.xwork2.util.ConfigParseUtil.toClassesSet;
@@ -68,9 +67,6 @@ public class OgnlUtil {
6867

6968
private static final Logger LOG = LogManager.getLogger(OgnlUtil.class);
7069

71-
// Flag used to reduce flooding logs with WARNs about using DevMode excluded packages
72-
private final AtomicBoolean warnReported = new AtomicBoolean(false);
73-
7470
private final OgnlCache<String, Object> expressionCache;
7571
private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
7672
private TypeConverter defaultConverter;
@@ -80,11 +76,6 @@ public class OgnlUtil {
8076
private boolean enableExpressionCache = true;
8177
private boolean enableEvalExpression;
8278

83-
private String devModeExcludedClasses = "";
84-
private String devModeExcludedPackageNamePatterns = "";
85-
private String devModeExcludedPackageNames = "";
86-
private String devModeExcludedPackageExemptClasses = "";
87-
8879
private Container container;
8980

9081
/**
@@ -164,9 +155,12 @@ protected void setExcludedClasses(String commaDelimitedClasses) {
164155
// Must be set directly on SecurityMemberAccess
165156
}
166157

167-
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
158+
/**
159+
* @deprecated since 6.5.0, no replacement.
160+
*/
161+
@Deprecated
168162
protected void setDevModeExcludedClasses(String commaDelimitedClasses) {
169-
this.devModeExcludedClasses = commaDelimitedClasses;
163+
// Must be set directly on SecurityMemberAccess
170164
}
171165

172166
/**
@@ -177,9 +171,12 @@ protected void setExcludedPackageNamePatterns(String commaDelimitedPackagePatter
177171
// Must be set directly on SecurityMemberAccess
178172
}
179173

180-
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
174+
/**
175+
* @deprecated since 6.5.0, no replacement.
176+
*/
177+
@Deprecated
181178
protected void setDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
182-
this.devModeExcludedPackageNamePatterns = commaDelimitedPackagePatterns;
179+
// Must be set directly on SecurityMemberAccess
183180
}
184181

185182
/**
@@ -190,9 +187,12 @@ protected void setExcludedPackageNames(String commaDelimitedPackageNames) {
190187
// Must be set directly on SecurityMemberAccess
191188
}
192189

193-
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
190+
/**
191+
* @deprecated since 6.5.0, no replacement.
192+
*/
193+
@Deprecated
194194
protected void setDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
195-
this.devModeExcludedPackageNames = commaDelimitedPackageNames;
195+
// Must be set directly on SecurityMemberAccess
196196
}
197197

198198
/**
@@ -203,9 +203,12 @@ public void setExcludedPackageExemptClasses(String commaDelimitedClasses) {
203203
// Must be set directly on SecurityMemberAccess
204204
}
205205

206-
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
206+
/**
207+
* @deprecated since 6.5.0, no replacement.
208+
*/
209+
@Deprecated
207210
public void setDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
208-
this.devModeExcludedPackageExemptClasses = commaDelimitedClasses;
211+
// Must be set directly on SecurityMemberAccess
209212
}
210213

211214
/**
@@ -856,6 +859,11 @@ protected Map<String, Object> createDefaultContext(Object root) {
856859
return createDefaultContext(root, null);
857860
}
858861

862+
/**
863+
* Note that the allowlist capability is not enforced by the {@link OgnlContext} returned by this method. Currently,
864+
* this context is only leveraged by some public methods on {@link OgnlUtil} which are called by
865+
* {@link OgnlReflectionProvider}.
866+
*/
859867
protected Map<String, Object> createDefaultContext(Object root, ClassResolver resolver) {
860868
if (resolver == null) {
861869
resolver = container.getInstance(RootAccessor.class);
@@ -867,17 +875,6 @@ protected Map<String, Object> createDefaultContext(Object root, ClassResolver re
867875
SecurityMemberAccess memberAccess = container.getInstance(SecurityMemberAccess.class);
868876
memberAccess.useEnforceAllowlistEnabled(Boolean.FALSE.toString());
869877

870-
if (devMode) {
871-
if (!warnReported.get()) {
872-
warnReported.set(true);
873-
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
874-
}
875-
memberAccess.useExcludedClasses(devModeExcludedClasses);
876-
memberAccess.useExcludedPackageNamePatterns(devModeExcludedPackageNamePatterns);
877-
memberAccess.useExcludedPackageNames(devModeExcludedPackageNames);
878-
memberAccess.useExcludedPackageExemptClasses(devModeExcludedPackageExemptClasses);
879-
}
880-
881878
return Ognl.createDefaultContext(root, memberAccess, resolver, defaultConverter);
882879
}
883880

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

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
import static java.util.Collections.emptySet;
5252
import static java.util.Collections.singletonList;
5353
import static java.util.Collections.unmodifiableSet;
54+
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
55+
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
5456

5557
/**
5658
* Allows access decisions to be made on the basis of whether a member is static or not.
@@ -77,16 +79,28 @@ public class SecurityMemberAccess implements MemberAccess {
7779

7880
private final ProviderAllowlist providerAllowlist;
7981
private final ThreadAllowlist threadAllowlist;
82+
8083
private boolean allowStaticFieldAccess = true;
84+
8185
private Set<Pattern> excludeProperties = emptySet();
8286
private Set<Pattern> acceptProperties = emptySet();
87+
8388
private Set<String> excludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
8489
private Set<Pattern> excludedPackageNamePatterns = emptySet();
8590
private Set<String> excludedPackageNames = emptySet();
8691
private Set<String> excludedPackageExemptClasses = emptySet();
92+
93+
private volatile boolean isDevModeInit;
94+
private boolean isDevMode;
95+
private Set<String> devModeExcludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
96+
private Set<Pattern> devModeExcludedPackageNamePatterns = emptySet();
97+
private Set<String> devModeExcludedPackageNames = emptySet();
98+
private Set<String> devModeExcludedPackageExemptClasses = emptySet();
99+
87100
private boolean enforceAllowlistEnabled = false;
88101
private Set<Class<?>> allowlistClasses = emptySet();
89102
private Set<String> allowlistPackageNames = emptySet();
103+
90104
private boolean disallowProxyObjectAccess = false;
91105
private boolean disallowProxyMemberAccess = false;
92106
private boolean disallowDefaultPackageAccess = false;
@@ -209,25 +223,71 @@ public boolean isAccessible(Map context, Object target, Member member, String pr
209223
* @return {@code true} if member access is allowed
210224
*/
211225
protected boolean checkAllowlist(Object target, Member member) {
212-
Class<?> memberClass = member.getDeclaringClass();
213226
if (!enforceAllowlistEnabled) {
227+
logAllowlistDisabled();
214228
return true;
215229
}
230+
231+
if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
232+
// If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
233+
// classes/members. This allows the allowlist capability to continue working and offer some level of
234+
// protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
235+
// entities. This is preferred to having to disable the allowlist capability entirely.
236+
Object newTarget = ProxyUtil.getHibernateProxyTarget(target);
237+
if (newTarget != target) {
238+
logAllowlistHibernateEntity(target, newTarget);
239+
target = newTarget;
240+
member = ProxyUtil.resolveTargetMember(member, newTarget);
241+
}
242+
}
243+
244+
Class<?> memberClass = member.getDeclaringClass();
216245
if (!isClassAllowlisted(memberClass)) {
217-
LOG.warn(format("Declaring class [{0}] of member type [{1}] is not allowlisted!", memberClass, member));
246+
LOG.warn("Declaring class [{}] of member type [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
247+
memberClass, member, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
218248
return false;
219249
}
220250
if (target == null || target.getClass() == memberClass) {
221251
return true;
222252
}
223253
Class<?> targetClass = target.getClass();
224254
if (!isClassAllowlisted(targetClass)) {
225-
LOG.warn(format("Target class [{0}] of target [{1}] is not allowlisted!", targetClass, target));
255+
LOG.warn("Target class [{}] of target [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
256+
targetClass, target, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
226257
return false;
227258
}
228259
return true;
229260
}
230261

262+
private void logAllowlistDisabled() {
263+
if (!isDevMode && !LOG.isDebugEnabled()) {
264+
return;
265+
}
266+
String msg = "OGNL allowlist is disabled!" +
267+
" We strongly recommend keeping it enabled to protect against critical vulnerabilities." +
268+
" Set the configuration `{0}=true` to enable it.";
269+
Object[] args = {StrutsConstants.STRUTS_ALLOWLIST_ENABLE};
270+
if (isDevMode) {
271+
LOG.warn(msg, args);
272+
} else {
273+
LOG.debug(msg, args);
274+
}
275+
}
276+
277+
private void logAllowlistHibernateEntity(Object original, Object resolved) {
278+
if (!isDevMode && !LOG.isDebugEnabled()) {
279+
return;
280+
}
281+
String msg = "Hibernate entity [{}] resolved to [{}] for purpose of OGNL allowlisting." +
282+
" We don't recommend executing OGNL expressions against Hibernate entities, you may disallow this behaviour using the configuration `{}=true`.";
283+
Object[] args = {original, resolved, StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS};
284+
if (isDevMode) {
285+
LOG.warn(msg, args);
286+
} else {
287+
LOG.debug(msg, args);
288+
}
289+
}
290+
231291
protected boolean isClassAllowlisted(Class<?> clazz) {
232292
return allowlistClasses.contains(clazz)
233293
|| ALLOWLIST_REQUIRED_CLASSES.contains(clazz)
@@ -241,6 +301,7 @@ protected boolean isClassAllowlisted(Class<?> clazz) {
241301
* @return {@code true} if member access is allowed
242302
*/
243303
protected boolean checkExclusionList(Object target, Member member) {
304+
useDevModeConfiguration();
244305
Class<?> memberClass = member.getDeclaringClass();
245306
if (isClassExcluded(memberClass)) {
246307
LOG.warn("Declaring class of member type [{}] is excluded!", memberClass);
@@ -436,12 +497,12 @@ public void useEnforceAllowlistEnabled(String enforceAllowlistEnabled) {
436497
this.enforceAllowlistEnabled = BooleanUtils.toBoolean(enforceAllowlistEnabled);
437498
}
438499

439-
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_CLASSES, required = false)
500+
@Inject(value = STRUTS_ALLOWLIST_CLASSES, required = false)
440501
public void useAllowlistClasses(String commaDelimitedClasses) {
441502
this.allowlistClasses = toClassObjectsSet(commaDelimitedClasses);
442503
}
443504

444-
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
505+
@Inject(value = STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
445506
public void useAllowlistPackageNames(String commaDelimitedPackageNames) {
446507
this.allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames);
447508
}
@@ -460,4 +521,41 @@ public void useDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
460521
public void useDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) {
461522
this.disallowDefaultPackageAccess = BooleanUtils.toBoolean(disallowDefaultPackageAccess);
462523
}
524+
525+
@Inject(StrutsConstants.STRUTS_DEVMODE)
526+
protected void useDevMode(String devMode) {
527+
this.isDevMode = BooleanUtils.toBoolean(devMode);
528+
}
529+
530+
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
531+
public void useDevModeExcludedClasses(String commaDelimitedClasses) {
532+
this.devModeExcludedClasses = toNewClassesSet(devModeExcludedClasses, commaDelimitedClasses);
533+
}
534+
535+
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
536+
public void useDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
537+
this.devModeExcludedPackageNamePatterns = toNewPatternsSet(devModeExcludedPackageNamePatterns, commaDelimitedPackagePatterns);
538+
}
539+
540+
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
541+
public void useDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
542+
this.devModeExcludedPackageNames = toNewPackageNamesSet(devModeExcludedPackageNames, commaDelimitedPackageNames);
543+
}
544+
545+
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
546+
public void useDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
547+
this.devModeExcludedPackageExemptClasses = toClassesSet(commaDelimitedClasses);
548+
}
549+
550+
private void useDevModeConfiguration() {
551+
if (!isDevMode || isDevModeInit) {
552+
return;
553+
}
554+
isDevModeInit = true;
555+
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
556+
excludedClasses = devModeExcludedClasses;
557+
excludedPackageNamePatterns = devModeExcludedPackageNamePatterns;
558+
excludedPackageNames = devModeExcludedPackageNames;
559+
excludedPackageExemptClasses = devModeExcludedPackageExemptClasses;
560+
}
463561
}
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 final 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+
}

0 commit comments

Comments
 (0)