Merge pull request #905 from apache/dependabot/maven/org.apache.maven.plugins-maven-failsafe-plugin-3.2.5
Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.0.0-M6 to 3.2.5
diff --git a/.github/workflows/scorecards-analysis.yaml b/.github/workflows/scorecards-analysis.yaml
index 67faabc..6867c45 100644
--- a/.github/workflows/scorecards-analysis.yaml
+++ b/.github/workflows/scorecards-analysis.yaml
@@ -57,7 +57,7 @@
publish_results: true
- name: "Upload artifact"
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # 4.3.1
+ uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # 4.3.3
with:
name: SARIF file
path: results.sarif
diff --git a/core/pom.xml b/core/pom.xml
index 3fcfaaf..8165d19 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -230,6 +230,15 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
+
+ <!-- Optional used in com.opensymphony.xwork2.util.ProxyUtil to detect if object is HibernateProxy -->
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-core</artifactId>
+ <version>5.6.15.Final</version>
+ <optional>true</optional>
+ </dependency>
+
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
diff --git a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
index 88790fc..8a4695e 100644
--- a/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
+++ b/core/src/main/java/com/opensymphony/xwork2/XWorkTestCase.java
@@ -36,6 +36,8 @@
import java.util.Locale;
import java.util.Map;
+import static java.util.Collections.singletonMap;
+
/**
* Base JUnit TestCase to extend for XWork specific JUnit tests. Uses
* the generic test setup for logic.
@@ -56,9 +58,7 @@
@Override
protected void setUp() throws Exception {
configurationManager = XWorkTestCaseHelper.setUp();
- configuration = configurationManager.getConfiguration();
- container = configuration.getContainer();
- actionProxyFactory = container.getInstance(ActionProxyFactory.class);
+ reloadConfiguration(configurationManager);
}
@Override
@@ -66,13 +66,17 @@
XWorkTestCaseHelper.tearDown(configurationManager);
}
- protected void loadConfigurationProviders(ConfigurationProvider... providers) {
- configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+ private void reloadConfiguration(ConfigurationManager configurationManager) {
configuration = configurationManager.getConfiguration();
container = configuration.getContainer();
actionProxyFactory = container.getInstance(ActionProxyFactory.class);
}
+ protected void loadConfigurationProviders(ConfigurationProvider... providers) {
+ configurationManager = XWorkTestCaseHelper.loadConfigurationProviders(configurationManager, providers);
+ reloadConfiguration(configurationManager);
+ }
+
protected void loadButSet(Map<String, ?> properties) {
loadConfigurationProviders(new StubConfigurationProvider() {
@Override
@@ -115,4 +119,25 @@
.getContextMap();
}
+ protected void setStrutsConstant(String constant, String value) {
+ setStrutsConstant(singletonMap(constant, value));
+ }
+
+ protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
+ configurationManager.addContainerProvider(new StubConfigurationProvider() {
+ @Override
+ public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
+ for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
+ props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+ });
+
+ configurationManager.reload();
+ reloadConfiguration(configurationManager);
+ }
}
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
index 9174e65..eafff83 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java
@@ -47,6 +47,7 @@
protected String name;
protected String namespace = "";
protected boolean isAbstract = false;
+ protected boolean isFinal = false; // a final package is unextendable
protected boolean needsRefresh;
protected boolean strictMethodInvocation = true;
@@ -69,6 +70,7 @@
this.name = orig.name;
this.namespace = orig.namespace;
this.isAbstract = orig.isAbstract;
+ this.isFinal = orig.isFinal;
this.needsRefresh = orig.needsRefresh;
this.actionConfigs = new LinkedHashMap<>(orig.actionConfigs);
this.globalResultConfigs = new LinkedHashMap<>(orig.globalResultConfigs);
@@ -85,6 +87,10 @@
return isAbstract;
}
+ public boolean isFinal() {
+ return isFinal;
+ }
+
public Map<String, ActionConfig> getActionConfigs() {
return actionConfigs;
}
@@ -360,6 +366,7 @@
PackageConfig that = (PackageConfig) o;
if (isAbstract != that.isAbstract) return false;
+ if (isFinal != that.isFinal) return false;
if (needsRefresh != that.needsRefresh) return false;
if (strictMethodInvocation != that.strictMethodInvocation) return false;
if (actionConfigs != null ? !actionConfigs.equals(that.actionConfigs) : that.actionConfigs != null)
@@ -404,6 +411,7 @@
result = 31 * result + name.hashCode();
result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
result = 31 * result + (isAbstract ? 1 : 0);
+ result = 31 * result + (isFinal ? 1 : 0);
result = 31 * result + (needsRefresh ? 1 : 0);
result = 31 * result + (strictMethodInvocation ? 1 : 0);
return result;
@@ -453,6 +461,11 @@
return this;
}
+ public Builder isFinal(boolean isFinal) {
+ target.isFinal = isFinal;
+ return this;
+ }
+
public Builder defaultInterceptorRef(String name) {
target.defaultInterceptorRef = name;
return this;
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
index 7bf0e7c..7c725e1 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java
@@ -120,6 +120,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -459,9 +460,12 @@
boolean appendNamedParameters = Boolean.parseBoolean(
container.getInstance(String.class, StrutsConstants.STRUTS_MATCHER_APPEND_NAMED_PARAMETERS)
);
+ boolean fallbackToEmptyNamespace = Boolean.parseBoolean(
+ Optional.ofNullable(container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE)).orElse("true")
+ );
return new RuntimeConfigurationImpl(Collections.unmodifiableMap(namespaceActionConfigs),
- Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters);
+ Collections.unmodifiableMap(namespaceConfigs), matcher, appendNamedParameters, fallbackToEmptyNamespace);
}
private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig packageContext) {
@@ -536,14 +540,17 @@
private final Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
private final NamespaceMatcher namespaceMatcher;
private final Map<String, String> namespaceConfigs;
+ private final boolean fallbackToEmptyNamespace;
public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs,
Map<String, String> namespaceConfigs,
PatternMatcher<int[]> matcher,
- boolean appendNamedParameters)
+ boolean appendNamedParameters,
+ boolean fallbackToEmptyNamespace)
{
this.namespaceActionConfigs = namespaceActionConfigs;
this.namespaceConfigs = namespaceConfigs;
+ this.fallbackToEmptyNamespace = fallbackToEmptyNamespace;
this.namespaceActionConfigMatchers = new LinkedHashMap<>();
this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet(), appendNamedParameters);
@@ -583,14 +590,17 @@
}
// fail over to empty namespace
- if (config == null && StringUtils.isNotBlank(namespace)) {
+ if (config == null && shouldFallbackToEmptyNamespace(namespace)) {
config = findActionConfigInNamespace("", name);
}
-
return config;
}
+ private boolean shouldFallbackToEmptyNamespace(String namespace) {
+ return StringUtils.isNotBlank(namespace) && ("/".equals(namespace) || fallbackToEmptyNamespace);
+ }
+
private ActionConfig findActionConfigInNamespace(String namespace, String name) {
ActionConfig config = null;
if (namespace == null) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
index cad52fb..6de2024 100644
--- a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlDocConfigurationProvider.java
@@ -603,8 +603,8 @@
*/
protected PackageConfig.Builder buildPackageContext(Element packageElement) {
String parent = packageElement.getAttribute("extends");
- String abstractVal = packageElement.getAttribute("abstract");
- boolean isAbstract = parseBoolean(abstractVal);
+ boolean isAbstract = parseBoolean(packageElement.getAttribute("abstract"));
+ boolean isFinal = parseBoolean(packageElement.getAttribute("final"));
String name = defaultString(packageElement.getAttribute("name"));
String namespace = defaultString(packageElement.getAttribute("namespace"));
@@ -617,6 +617,7 @@
PackageConfig.Builder cfg = new PackageConfig.Builder(name)
.namespace(namespace)
.isAbstract(isAbstract)
+ .isFinal(isFinal)
.strictMethodInvocation(strictDMI)
.location(DomHelper.getLocationObject(packageElement));
@@ -627,17 +628,23 @@
// has parents, let's look it up
List<PackageConfig> parents = new ArrayList<>();
for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) {
- if (configuration.getPackageConfigNames().contains(parentPackageName)) {
- parents.add(configuration.getPackageConfig(parentPackageName));
- } else if (declaredPackages.containsKey(parentPackageName)) {
- if (configuration.getPackageConfig(parentPackageName) == null) {
- addPackage(declaredPackages.get(parentPackageName));
+ boolean isParentPackageConfigDefined = false;
+ if (configuration.getPackageConfigNames().contains(parentPackageName)) { // parent package already added to configuration
+ isParentPackageConfigDefined = true;
+ } else if (declaredPackages.containsKey(parentPackageName)) { // parent package declared but yet added to configuration
+ addPackage(declaredPackages.get(parentPackageName));
+ isParentPackageConfigDefined = true;
+ }
+
+ if (isParentPackageConfigDefined) {
+ PackageConfig parentPackageConfig = configuration.getPackageConfig(parentPackageName);
+ if (parentPackageConfig.isFinal()) {
+ throw new ConfigurationException("Parent package is final and unextendable: " + parentPackageName);
}
- parents.add(configuration.getPackageConfig(parentPackageName));
+ parents.add(parentPackageConfig);
} else {
throw new ConfigurationException("Parent package is not defined: " + parentPackageName);
}
-
}
if (parents.isEmpty()) {
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
index 29dc7fc..e503f49 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/DefaultOgnlCacheFactory.java
@@ -32,6 +32,7 @@
private CacheType defaultCacheType;
private int cacheMaxSize;
+ private final int initialCapacity;
/**
* @deprecated since 6.4.0, use {@link #DefaultOgnlCacheFactory(int, CacheType)}
@@ -42,13 +43,18 @@
}
public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType) {
+ this(cacheMaxSize, defaultCacheType, DEFAULT_INIT_CAPACITY);
+ }
+
+ public DefaultOgnlCacheFactory(int cacheMaxSize, CacheType defaultCacheType, int initialCapacity) {
this.cacheMaxSize = cacheMaxSize;
this.defaultCacheType = defaultCacheType;
+ this.initialCapacity = initialCapacity;
}
@Override
public OgnlCache<Key, Value> buildOgnlCache() {
- return buildOgnlCache(getCacheMaxSize(), DEFAULT_INIT_CAPACITY, DEFAULT_LOAD_FACTOR, defaultCacheType);
+ return buildOgnlCache(getCacheMaxSize(), initialCapacity, DEFAULT_LOAD_FACTOR, defaultCacheType);
}
@Override
diff --git a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
index 510a65c..43ae992 100644
--- a/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
+++ b/core/src/main/java/com/opensymphony/xwork2/ognl/SecurityMemberAccess.java
@@ -31,7 +31,6 @@
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
-import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
@@ -87,6 +86,7 @@
private boolean enforceAllowlistEnabled = false;
private Set<Class<?>> allowlistClasses = emptySet();
private Set<String> allowlistPackageNames = emptySet();
+ private boolean disallowProxyObjectAccess = false;
private boolean disallowProxyMemberAccess = false;
private boolean disallowDefaultPackageAccess = false;
@@ -160,6 +160,11 @@
}
}
+ if (!checkProxyObjectAccess(target)) {
+ LOG.warn("Access to proxy is blocked! Target [{}], proxy class [{}]", target, target.getClass().getName());
+ return false;
+ }
+
if (!checkProxyMemberAccess(target, member)) {
LOG.warn("Access to proxy is blocked! Member class [{}] of target [{}], member [{}]", member.getDeclaringClass(), target, member);
return false;
@@ -286,7 +291,14 @@
}
/**
- * @return {@code true} if member access is allowed
+ * @return {@code true} if proxy object access is allowed
+ */
+ protected boolean checkProxyObjectAccess(Object target) {
+ return !(disallowProxyObjectAccess && ProxyUtil.isProxy(target));
+ }
+
+ /**
+ * @return {@code true} if proxy member access is allowed
*/
protected boolean checkProxyMemberAccess(Object target, Member member) {
return !(disallowProxyMemberAccess && ProxyUtil.isProxyMember(member, target));
@@ -300,10 +312,6 @@
* @return {@code true} if member access is allowed
*/
protected boolean checkStaticMethodAccess(Member member) {
- if (checkEnumAccess(member)) {
- LOG.trace("Exempting Enum#values from static method check: class [{}]", member.getDeclaringClass());
- return true;
- }
return member instanceof Field || !isStatic(member);
}
@@ -334,17 +342,6 @@
return Modifier.isPublic(member.getModifiers());
}
- /**
- * @return {@code true} if member access is allowed
- */
- protected boolean checkEnumAccess(Member member) {
- return member.getDeclaringClass().isEnum()
- && isStatic(member)
- && member instanceof Method
- && member.getName().equals("values")
- && ((Method) member).getParameterCount() == 0;
- }
-
protected boolean isPackageExcluded(Class<?> clazz) {
return !excludedPackageExemptClasses.contains(clazz.getName()) && (isExcludedPackageNames(clazz) || isExcludedPackageNamePatterns(clazz));
}
@@ -448,6 +445,11 @@
this.allowlistPackageNames = toPackageNamesSet(commaDelimitedPackageNames);
}
+ @Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, required = false)
+ public void useDisallowProxyObjectAccess(String disallowProxyObjectAccess) {
+ this.disallowProxyObjectAccess = BooleanUtils.toBoolean(disallowProxyObjectAccess);
+ }
+
@Inject(value = StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, required = false)
public void useDisallowProxyMemberAccess(String disallowProxyMemberAccess) {
this.disallowProxyMemberAccess = BooleanUtils.toBoolean(disallowProxyMemberAccess);
diff --git a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
index 9b0e7d4..c169af2 100644
--- a/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
+++ b/core/src/main/java/com/opensymphony/xwork2/util/ProxyUtil.java
@@ -18,13 +18,20 @@
*/
package com.opensymphony.xwork2.util;
+import com.opensymphony.xwork2.ognl.DefaultOgnlCacheFactory;
+import com.opensymphony.xwork2.ognl.OgnlCache;
+import com.opensymphony.xwork2.ognl.OgnlCacheFactory;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
+import org.hibernate.proxy.HibernateProxy;
-import java.lang.reflect.*;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
/**
* <code>ProxyUtil</code>
@@ -38,11 +45,13 @@
private static final String SPRING_SPRINGPROXY_CLASS_NAME = "org.springframework.aop.SpringProxy";
private static final String SPRING_SINGLETONTARGETSOURCE_CLASS_NAME = "org.springframework.aop.target.SingletonTargetSource";
private static final String SPRING_TARGETCLASSAWARE_CLASS_NAME = "org.springframework.aop.TargetClassAware";
-
- private static final Map<Class<?>, Boolean> isProxyCache =
- new ConcurrentHashMap<>(256);
- private static final Map<Member, Boolean> isProxyMemberCache =
- new ConcurrentHashMap<>(256);
+ private static final String HIBERNATE_HIBERNATEPROXY_CLASS_NAME = "org.hibernate.proxy.HibernateProxy";
+ private static final int CACHE_MAX_SIZE = 10000;
+ private static final int CACHE_INITIAL_CAPACITY = 256;
+ private static final OgnlCache<Class<?>, Boolean> isProxyCache = new DefaultOgnlCacheFactory<Class<?>, Boolean>(
+ CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
+ private static final OgnlCache<Member, Boolean> isProxyMemberCache = new DefaultOgnlCacheFactory<Member, Boolean>(
+ CACHE_MAX_SIZE, OgnlCacheFactory.CacheType.WTLFU, CACHE_INITIAL_CAPACITY).buildOgnlCache();
/**
* Determine the ultimate target class of the given instance, traversing
@@ -75,7 +84,7 @@
return flag;
}
- boolean isProxy = isSpringAopProxy(object);
+ boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
isProxyCache.put(clazz, isProxy);
return isProxy;
@@ -87,7 +96,7 @@
* @param object the object to check
*/
public static boolean isProxyMember(Member member, Object object) {
- if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object)) {
+ if (!Modifier.isStatic(member.getModifiers()) && !isProxy(object) && !isHibernateProxy(object)) {
return false;
}
@@ -96,13 +105,41 @@
return flag;
}
- boolean isProxyMember = isSpringProxyMember(member);
+ boolean isProxyMember = isSpringProxyMember(member) || isHibernateProxyMember(member);
isProxyMemberCache.put(member, isProxyMember);
return isProxyMember;
}
/**
+ * Check whether the given object is a Hibernate proxy.
+ *
+ * @param object the object to check
+ */
+ public static boolean isHibernateProxy(Object object) {
+ try {
+ return HibernateProxy.class.isAssignableFrom(object.getClass());
+ } catch (NoClassDefFoundError ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the given member is a member of a Hibernate proxy.
+ *
+ * @param member the member to check
+ */
+ public static boolean isHibernateProxyMember(Member member) {
+ try {
+ Class<?> clazz = ClassLoaderUtil.loadClass(HIBERNATE_HIBERNATEPROXY_CLASS_NAME, ProxyUtil.class);
+ return hasMember(clazz, member);
+ } catch (ClassNotFoundException ignored) {
+ }
+
+ return false;
+ }
+
+ /**
* Determine the ultimate target class of the given spring bean instance, traversing
* not only a top-level spring proxy but any number of nested spring proxies as well —
* as long as possible without side effects, that is, just for singleton targets.
diff --git a/core/src/main/java/org/apache/struts2/StrutsConstants.java b/core/src/main/java/org/apache/struts2/StrutsConstants.java
index 3d0d1a0..91b8eb2 100644
--- a/core/src/main/java/org/apache/struts2/StrutsConstants.java
+++ b/core/src/main/java/org/apache/struts2/StrutsConstants.java
@@ -230,6 +230,8 @@
public static final String STRUTS_XWORKCONVERTER = "struts.xworkConverter";
public static final String STRUTS_ALWAYS_SELECT_FULL_NAMESPACE = "struts.mapper.alwaysSelectFullNamespace";
+ /** Fallback to empty namespace when request namespace didn't match any in action configuration */
+ public static final String STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE = "struts.actionConfig.fallbackToEmptyNamespace";
/** The {@link com.opensymphony.xwork2.LocaleProviderFactory} implementation class */
public static final String STRUTS_LOCALE_PROVIDER_FACTORY = "struts.localeProviderFactory";
@@ -480,6 +482,7 @@
public static final String STRUTS_TEXT_PROVIDER_FACTORY = "struts.textProviderFactory";
public static final String STRUTS_LOCALIZED_TEXT_PROVIDER = "struts.localizedTextProvider";
+ public static final String STRUTS_DISALLOW_PROXY_OBJECT_ACCESS = "struts.disallowProxyObjectAccess";
public static final String STRUTS_DISALLOW_PROXY_MEMBER_ACCESS = "struts.disallowProxyMemberAccess";
public static final String STRUTS_DISALLOW_DEFAULT_PACKAGE_ACCESS = "struts.disallowDefaultPackageAccess";
diff --git a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
index 9b62b3d..9c1b394 100644
--- a/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
+++ b/core/src/main/java/org/apache/struts2/config/StrutsXmlConfigurationProvider.java
@@ -54,6 +54,7 @@
put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd");
+ put("-//Apache Software Foundation//DTD Struts Configuration 6.5//EN", "struts-6.5.dtd");
}});
private File baseDir = null;
private final String filename;
diff --git a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
index 2b85424..11b30c2 100644
--- a/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
+++ b/core/src/main/java/org/apache/struts2/config/entities/ConstantConfig.java
@@ -90,6 +90,7 @@
private Boolean freemarkerWrapperAltMap;
private BeanConfig xworkConverter;
private Boolean mapperAlwaysSelectFullNamespace;
+ private Boolean actionConfigFallbackToEmptyNamespace;
private BeanConfig localeProviderFactory;
private String mapperIdParameterName;
private Boolean ognlAllowStaticFieldAccess;
@@ -145,6 +146,7 @@
private String strictMethodInvocationMethodRegex;
private BeanConfig textProviderFactory;
private BeanConfig localizedTextProvider;
+ private Boolean disallowProxyObjectAccess;
private Boolean disallowProxyMemberAccess;
private Integer ognlAutoGrowthCollectionLimit;
private String staticContentPath;
@@ -225,6 +227,7 @@
map.put(StrutsConstants.STRUTS_FREEMARKER_WRAPPER_ALT_MAP, Objects.toString(freemarkerWrapperAltMap, null));
map.put(StrutsConstants.STRUTS_XWORKCONVERTER, beanConfToString(xworkConverter));
map.put(StrutsConstants.STRUTS_ALWAYS_SELECT_FULL_NAMESPACE, Objects.toString(mapperAlwaysSelectFullNamespace, null));
+ map.put(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, Objects.toString(actionConfigFallbackToEmptyNamespace, null));
map.put(StrutsConstants.STRUTS_LOCALE_PROVIDER_FACTORY, beanConfToString(localeProviderFactory));
map.put(StrutsConstants.STRUTS_ID_PARAMETER_NAME, mapperIdParameterName);
map.put(StrutsConstants.STRUTS_ALLOW_STATIC_FIELD_ACCESS, Objects.toString(ognlAllowStaticFieldAccess, null));
@@ -279,6 +282,7 @@
map.put(StrutsConstants.STRUTS_SMI_METHOD_REGEX, strictMethodInvocationMethodRegex);
map.put(StrutsConstants.STRUTS_TEXT_PROVIDER_FACTORY, beanConfToString(textProviderFactory));
map.put(StrutsConstants.STRUTS_LOCALIZED_TEXT_PROVIDER, beanConfToString(localizedTextProvider));
+ map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS, Objects.toString(disallowProxyObjectAccess, null));
map.put(StrutsConstants.STRUTS_DISALLOW_PROXY_MEMBER_ACCESS, Objects.toString(disallowProxyMemberAccess, null));
map.put(StrutsConstants.STRUTS_OGNL_AUTO_GROWTH_COLLECTION_LIMIT, Objects.toString(ognlAutoGrowthCollectionLimit, null));
map.put(StrutsConstants.STRUTS_UI_STATIC_CONTENT_PATH, Objects.toString(staticContentPath, StaticContentLoader.DEFAULT_STATIC_CONTENT_PATH));
@@ -812,6 +816,14 @@
this.mapperAlwaysSelectFullNamespace = mapperAlwaysSelectFullNamespace;
}
+ public Boolean getActionConfigFallbackToEmptyNamespace() {
+ return actionConfigFallbackToEmptyNamespace;
+ }
+
+ public void setActionConfigFallbackToEmptyNamespace(Boolean actionConfigFallbackToEmptyNamespace) {
+ this.actionConfigFallbackToEmptyNamespace = actionConfigFallbackToEmptyNamespace;
+ }
+
public BeanConfig getLocaleProviderFactory() {
return localeProviderFactory;
}
@@ -1360,6 +1372,14 @@
this.localizedTextProvider = new BeanConfig(clazz, clazz.getName());
}
+ public Boolean getDisallowProxyObjectAccess() {
+ return disallowProxyObjectAccess;
+ }
+
+ public void setDisallowProxyObjectAccess(Boolean disallowProxyObjectAccess) {
+ this.disallowProxyObjectAccess = disallowProxyObjectAccess;
+ }
+
public Boolean getDisallowProxyMemberAccess() {
return disallowProxyMemberAccess;
}
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
index 5bad0b4..a1f9094 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java
@@ -78,6 +78,7 @@
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -88,6 +89,10 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+import static java.util.stream.Collectors.toList;
+
/**
* A utility class the actual dispatcher delegates most of its tasks to. Each instance
* of the primary dispatcher holds an instance of this dispatcher to be shared for
@@ -162,6 +167,10 @@
*/
private Pattern multipartValidationPattern = Pattern.compile(MULTIPART_FORM_DATA_REGEX);
+ private String actionExcludedPatternsStr;
+ private String actionExcludedPatternsSeparator = ",";
+ private List<Pattern> actionExcludedPatterns;
+
/**
* Provide list of default configuration files.
*/
@@ -340,6 +349,33 @@
this.multipartValidationPattern = Pattern.compile(multipartValidationRegex);
}
+ @Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, required = false)
+ public void setActionExcludedPatternsSeparator(String separator) {
+ this.actionExcludedPatternsSeparator = separator;
+ }
+
+ @Inject(value = StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, required = false)
+ public void setActionExcludedPatterns(String excludedPatterns) {
+ this.actionExcludedPatternsStr = excludedPatterns;
+ }
+
+ public List<Pattern> getActionExcludedPatterns() {
+ if (actionExcludedPatterns == null) {
+ initActionExcludedPatterns();
+ }
+ return actionExcludedPatterns;
+ }
+
+ private void initActionExcludedPatterns() {
+ if (actionExcludedPatternsStr == null || actionExcludedPatternsStr.trim().isEmpty()) {
+ actionExcludedPatterns = emptyList();
+ return;
+ }
+ actionExcludedPatterns = unmodifiableList(
+ Arrays.stream(actionExcludedPatternsStr.split(actionExcludedPatternsSeparator))
+ .map(String::trim).map(Pattern::compile).collect(toList()));
+ }
+
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
index 367aeba..f5cf21a 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/InitOperations.java
@@ -19,10 +19,7 @@
package org.apache.struts2.dispatcher;
import com.opensymphony.xwork2.ActionContext;
-import org.apache.struts2.StrutsConstants;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -100,27 +97,11 @@
* @param dispatcher The dispatcher to check for exclude pattern configuration
* @return a List of Patterns for request to exclude if apply, or <tt>null</tt>
* @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN
+ * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns()} instead.
*/
+ @Deprecated
public List<Pattern> buildExcludedPatternsList(Dispatcher dispatcher) {
- String excludePatterns = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN);
- String separator = dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
- if (separator == null) {
- separator = ",";
- }
- return buildExcludedPatternsList(excludePatterns, separator);
- }
-
- private List<Pattern> buildExcludedPatternsList(String patterns, String separator) {
- if (null != patterns && patterns.trim().length() != 0) {
- List<Pattern> list = new ArrayList<>();
- String[] tokens = patterns.split(separator);
- for (String token : tokens) {
- list.add(Pattern.compile(token.trim()));
- }
- return Collections.unmodifiableList(list);
- } else {
- return null;
- }
+ return dispatcher.getActionExcludedPatterns();
}
}
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
index 6888c5b..ab43235 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/PrepareOperations.java
@@ -223,21 +223,23 @@
* Check whether the request matches a list of exclude patterns.
*
* @param request The request to check patterns against
- * @param excludedPatterns list of patterns for exclusion
- *
* @return <tt>true</tt> if the request URI matches one of the given patterns
*/
+ public boolean isUrlExcluded(HttpServletRequest request) {
+ String uri = RequestUtils.getUri(request);
+ return dispatcher.getActionExcludedPatterns().stream().anyMatch(pattern -> pattern.matcher(uri).matches());
+ }
+
+ /**
+ * @deprecated since 6.4.0, use {@link #isUrlExcluded(HttpServletRequest)} instead.
+ */
+ @Deprecated
public boolean isUrlExcluded(HttpServletRequest request, List<Pattern> excludedPatterns) {
if (excludedPatterns == null) {
return false;
}
String uri = RequestUtils.getUri(request);
- for (Pattern pattern : excludedPatterns) {
- if (pattern.matcher(uri).matches()) {
- return true;
- }
- }
- return false;
+ return excludedPatterns.stream().anyMatch(pattern -> pattern.matcher(uri).matches());
}
/**
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
index e91a1b9..bce6ec4 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareAndExecuteFilter.java
@@ -50,6 +50,12 @@
protected PrepareOperations prepare;
protected ExecuteOperations execute;
+
+ /**
+ * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns} or
+ * {@link PrepareOperations#isUrlExcluded(HttpServletRequest)} instead.
+ */
+ @Deprecated
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
@@ -62,7 +68,7 @@
prepare = createPrepareOperations(dispatcher);
execute = createExecuteOperations(dispatcher);
- // Note: Currently, excluded patterns are not refreshed following an XWork config reload
+
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
@@ -121,7 +127,7 @@
try {
prepare.trackRecursion(request);
String uri = RequestUtils.getUri(request);
- if (prepare.isUrlExcluded(request, excludedPatterns)) {
+ if (prepare.isUrlExcluded(request)) {
LOG.trace("Request: {} is excluded from handling by Struts, passing request to other filters", uri);
chain.doFilter(request, response);
} else {
diff --git a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
index 81dffb6..475a139 100644
--- a/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
+++ b/core/src/main/java/org/apache/struts2/dispatcher/filter/StrutsPrepareFilter.java
@@ -43,6 +43,12 @@
protected static final String REQUEST_EXCLUDED_FROM_ACTION_MAPPING = StrutsPrepareFilter.class.getName() + ".REQUEST_EXCLUDED_FROM_ACTION_MAPPING";
protected PrepareOperations prepare;
+
+ /**
+ * @deprecated since 6.4.0, use {@link Dispatcher#getActionExcludedPatterns} or
+ * {@link PrepareOperations#isUrlExcluded(HttpServletRequest)} instead.
+ */
+ @Deprecated
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
@@ -53,7 +59,7 @@
dispatcher = init.initDispatcher(config);
prepare = createPrepareOperations(dispatcher);
- // Note: Currently, excluded patterns are not refreshed following an XWork config reload
+
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
@@ -102,7 +108,7 @@
boolean didWrap = false;
try {
prepare.trackRecursion(request);
- if (prepare.isUrlExcluded(request, excludedPatterns)) {
+ if (prepare.isUrlExcluded(request)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, true);
} else {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, false);
diff --git a/core/src/main/resources/org/apache/struts2/default.properties b/core/src/main/resources/org/apache/struts2/default.properties
index 96c5459..8b7226a 100644
--- a/core/src/main/resources/org/apache/struts2/default.properties
+++ b/core/src/main/resources/org/apache/struts2/default.properties
@@ -215,6 +215,9 @@
### Whether to always select the namespace to be everything before the last slash or not
struts.mapper.alwaysSelectFullNamespace=false
+### Whether to fallback to empty namespace when request namespace does not match any in configuration
+struts.actionConfig.fallbackToEmptyNamespace=true
+
### Whether to allow static field access in OGNL expressions or not
struts.ognl.allowStaticFieldAccess=true
diff --git a/core/src/main/resources/struts-6.5.dtd b/core/src/main/resources/struts-6.5.dtd
new file mode 100644
index 0000000..6c2ef0d
--- /dev/null
+++ b/core/src/main/resources/struts-6.5.dtd
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<!-- START SNIPPET: strutsDtd -->
+
+<!--
+ Struts configuration DTD.
+ Use the following DOCTYPE
+
+ <!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+ "https://struts.apache.org/dtds/struts-6.5.dtd">
+-->
+
+<!ELEMENT struts ((package|include|bean|constant)*,bean-selection?, unknown-handler-stack?)>
+<!ATTLIST struts
+ order CDATA #IMPLIED
+>
+
+<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-allowed-methods?, global-exception-mappings?, action*)>
+<!ATTLIST package
+ name CDATA #REQUIRED
+ extends CDATA #IMPLIED
+ namespace CDATA #IMPLIED
+ abstract CDATA #IMPLIED
+ final CDATA #IMPLIED
+ strict-method-invocation (true|false) "true"
+>
+
+<!ELEMENT result-types (result-type+)>
+
+<!ELEMENT result-type (param*)>
+<!ATTLIST result-type
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+ default (true|false) "false"
+>
+
+<!ELEMENT interceptors (interceptor|interceptor-stack)+>
+
+<!ELEMENT interceptor (param*)>
+<!ATTLIST interceptor
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT interceptor-stack (interceptor-ref*)>
+<!ATTLIST interceptor-stack
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT interceptor-ref (param*)>
+<!ATTLIST interceptor-ref
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT default-interceptor-ref (#PCDATA)>
+<!ATTLIST default-interceptor-ref
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT default-action-ref (#PCDATA)>
+<!ATTLIST default-action-ref
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT default-class-ref (#PCDATA)>
+<!ATTLIST default-class-ref
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT global-results (result+)>
+
+<!ELEMENT global-allowed-methods (#PCDATA)>
+
+<!ELEMENT global-exception-mappings (exception-mapping+)>
+
+<!ELEMENT action ((param|result|interceptor-ref|exception-mapping)*,allowed-methods?)>
+<!ATTLIST action
+ name CDATA #REQUIRED
+ class CDATA #IMPLIED
+ method CDATA #IMPLIED
+ converter CDATA #IMPLIED
+>
+
+<!ELEMENT param (#PCDATA)>
+<!ATTLIST param
+ name CDATA #REQUIRED
+>
+
+<!ELEMENT result (#PCDATA|param)*>
+<!ATTLIST result
+ name CDATA #IMPLIED
+ type CDATA #IMPLIED
+>
+
+<!ELEMENT exception-mapping (#PCDATA|param)*>
+<!ATTLIST exception-mapping
+ name CDATA #IMPLIED
+ exception CDATA #REQUIRED
+ result CDATA #REQUIRED
+>
+
+<!ELEMENT allowed-methods (#PCDATA)>
+
+<!ELEMENT include (#PCDATA)>
+<!ATTLIST include
+ file CDATA #REQUIRED
+>
+
+<!ELEMENT bean (#PCDATA)>
+<!ATTLIST bean
+ type CDATA #IMPLIED
+ name CDATA #IMPLIED
+ class CDATA #REQUIRED
+ scope CDATA #IMPLIED
+ static CDATA #IMPLIED
+ optional CDATA #IMPLIED
+>
+
+<!ELEMENT bean-selection (#PCDATA)>
+<!ATTLIST bean-selection
+ name CDATA #IMPLIED
+ class CDATA #IMPLIED
+>
+
+<!ELEMENT constant (#PCDATA)>
+<!ATTLIST constant
+ name CDATA #REQUIRED
+ value CDATA #REQUIRED
+>
+
+<!ELEMENT unknown-handler-stack (unknown-handler-ref*)>
+<!ELEMENT unknown-handler-ref (#PCDATA)>
+<!ATTLIST unknown-handler-ref
+ name CDATA #REQUIRED
+>
+
+<!-- END SNIPPET: strutsDtd -->
+
diff --git a/core/src/main/resources/struts-excluded-classes.xml b/core/src/main/resources/struts-excluded-classes.xml
index f3f4f3f..58b89aa 100644
--- a/core/src/main/resources/struts-excluded-classes.xml
+++ b/core/src/main/resources/struts-excluded-classes.xml
@@ -92,6 +92,7 @@
org.apache.catalina.core,
org.apache.commons.beanutils,
org.apache.commons.collections,
+ org.apache.jasper,
org.apache.struts2.ognl,
org.apache.tomcat,
org.apache.velocity,
@@ -124,6 +125,7 @@
org.apache.catalina.core,
org.apache.commons.beanutils,
org.apache.commons.collections,
+ org.apache.jasper,
org.apache.struts2.ognl,
org.apache.tomcat,
org.apache.velocity,
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
index b52b9d4..520f8c2 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/ConfigurationTest.java
@@ -31,6 +31,7 @@
import com.opensymphony.xwork2.mock.MockInterceptor;
import com.opensymphony.xwork2.test.StubConfigurationProvider;
import com.opensymphony.xwork2.util.location.LocatableProperties;
+import org.apache.struts2.StrutsConstants;
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
import org.apache.struts2.dispatcher.HttpParameters;
@@ -239,6 +240,41 @@
mockContainerProvider.verify();
}
+ public void testGetActionConfigFallbackToEmptyNamespaceWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsEnabled() {
+ // struts.actionConfig.fallbackToEmptyNamespace default to true, so it is enabled
+ RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
+
+ // check namespace that doesn't match fallback to empty namespace
+ ActionConfig actionConfig = configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo");
+ assertEquals("default", actionConfig.getPackageName()); // fallback to empty namespace (package name is default)
+ assertEquals("LazyFoo", actionConfig.getName());
+
+ // check non-empty namespace and name in config still matches
+ assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
+
+ // check root namespace and name in config still matches
+ actionConfig = configuration.getActionConfig("/", "LazyFoo");
+ assertEquals("default", actionConfig.getPackageName());
+ assertEquals("LazyFoo", actionConfig.getName());
+ }
+
+ public void testGetActionConfigReturnNullWhenNamespaceDontMatchAndEmptyNamespaceFallbackIsDisabled() {
+ // set the struts.actionConfig.fallbackToEmptyNamespace to false and reload the configuration
+ setStrutsConstant(StrutsConstants.STRUTS_ACTION_CONFIG_FALLBACK_TO_EMPTY_NAMESPACE, "false");
+ RuntimeConfiguration configuration = configurationManager.getConfiguration().getRuntimeConfiguration();
+
+ // check namespace that doesn't match NOT fallback to empty namespace and return null
+ assertNull(configuration.getActionConfig("/something/that/is/not/in/the/namespace/config", "LazyFoo"));
+
+ // check non-empty namespace and name in config still matches
+ assertNotNull(configuration.getActionConfig("includeTest", "Foo"));
+
+ // check root namespace and name in config still matches
+ ActionConfig actionConfig = configuration.getActionConfig("/", "LazyFoo");
+ assertEquals("default", actionConfig.getPackageName());
+ assertEquals("LazyFoo", actionConfig.getName());
+ }
+
public void testInitForPackageProviders() {
loadConfigurationProviders(new StubConfigurationProvider() {
diff --git a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
index b39baec..ed8910b 100644
--- a/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProviderPackagesTest.java
@@ -36,10 +36,9 @@
public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase {
public void testBadInheritance() throws ConfigurationException {
- final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-bad-inheritance.xml";
ConfigurationProvider provider = null;
try {
- provider = buildConfigurationProvider(filename);
+ provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-bad-inheritance.xml"));
fail("Should have thrown a ConfigurationException");
provider.init(configuration);
provider.loadPackages();
@@ -49,8 +48,7 @@
}
public void testBasicPackages() throws ConfigurationException {
- final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-basic-packages.xml";
- ConfigurationProvider provider = buildConfigurationProvider(filename);
+ ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-basic-packages.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -70,8 +68,7 @@
}
public void testDefaultPackage() throws ConfigurationException {
- final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-default-package.xml";
- ConfigurationProvider provider = buildConfigurationProvider(filename);
+ ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-default-package.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -84,8 +81,7 @@
}
public void testPackageInheritance() throws ConfigurationException {
- final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-package-inheritance.xml";
- ConfigurationProvider provider = buildConfigurationProvider(filename);
+ ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-inheritance.xml"));
provider.init(configuration);
provider.loadPackages();
@@ -111,7 +107,7 @@
assertTrue(multipleParents.contains(defaultPackage));
assertTrue(multipleParents.contains(abstractPackage));
assertTrue(multipleParents.contains(singlePackage));
-
+
PackageConfig parentBelow = configuration.getPackageConfig("testParentBelow");
assertEquals(1, parentBelow.getParents().size());
List<PackageConfig> parentBelowParents = parentBelow.getParents();
@@ -129,7 +125,7 @@
assertNull(runtimeConfiguration.getActionConfig("/single", "abstract"));
assertNotNull(runtimeConfiguration.getActionConfig("/single", "single"));
assertNull(runtimeConfiguration.getActionConfig("/single", "multiple"));
-
+
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "default"));
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "abstract"));
assertNotNull(runtimeConfiguration.getActionConfig("/parentBelow", "single"));
@@ -138,13 +134,57 @@
}
+ public void testPackageWithFinalAttributeLoads() throws ConfigurationException {
+ ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-final.xml"));
+
+ provider.init(configuration);
+ provider.loadPackages();
+
+ // test expectations
+ assertEquals(3, configuration.getPackageConfigs().size());
+ PackageConfig defaultPackage = configuration.getPackageConfig("default");
+ assertNotNull(defaultPackage);
+ assertEquals("default", defaultPackage.getName());
+
+ // final package extends default
+ PackageConfig finalPackage = configuration.getPackageConfig("finalPackage");
+ assertNotNull(finalPackage);
+ assertEquals("finalPackage", finalPackage.getName());
+ assertEquals(1, finalPackage.getParents().size());
+ assertEquals(defaultPackage, finalPackage.getParents().get(0));
+
+ // normal package extends default
+ PackageConfig normalPackage = configuration.getPackageConfig("normalPackage");
+ assertNotNull(normalPackage);
+ assertEquals("normalPackage", normalPackage.getName());
+ assertEquals(1, normalPackage.getParents().size());
+ assertEquals(defaultPackage, normalPackage.getParents().get(0));
+
+ configurationManager.addContainerProvider(provider);
+ configurationManager.reload();
+
+ RuntimeConfiguration runtimeConfiguration = configurationManager.getConfiguration().getRuntimeConfiguration();
+ assertNotNull(runtimeConfiguration.getActionConfig("/final", "default"));
+ assertNotNull(runtimeConfiguration.getActionConfig("/final", "actionFinal"));
+
+ assertNotNull(runtimeConfiguration.getActionConfig("/normal", "default"));
+ assertNotNull(runtimeConfiguration.getActionConfig("/normal", "actionNormal"));
+ }
+
+ public void testExtendsFinalPackageThrowsConfigurationException() throws ConfigurationException {
+ try {
+ buildConfigurationProvider(getXmlConfigFilePath("xwork-test-package-extends-final.xml"));
+ } catch (ConfigurationException e) {
+ assertEquals("Parent package is final and unextendable: parentLevelTwo", e.getMessage());
+ }
+ }
+
public void testDefaultClassRef() throws ConfigurationException {
- final String filename = "com/opensymphony/xwork2/config/providers/xwork-test-defaultclassref-package.xml";
final String hasDefaultClassRefPkgName = "hasDefaultClassRef";
final String noDefaultClassRefPkgName = "noDefaultClassRef";
final String testDefaultClassRef = "com.opensymphony.xwork2.ActionSupport";
- ConfigurationProvider provider = buildConfigurationProvider(filename);
+ ConfigurationProvider provider = buildConfigurationProvider(getXmlConfigFilePath("xwork-test-defaultclassref-package.xml"));
provider.init(configuration);
// setup our expectations
@@ -157,4 +197,8 @@
assertEquals(expectedDefaultClassRefPackage, configuration.getPackageConfig(hasDefaultClassRefPkgName));
assertEquals(expectedNoDefaultClassRefPackage, configuration.getPackageConfig(noDefaultClassRefPkgName));
}
+
+ private String getXmlConfigFilePath(String fileName) {
+ return "com/opensymphony/xwork2/config/providers/" + fileName;
+ }
}
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
index 3bdfd67..7fb560c 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/OgnlValueStackTest.java
@@ -437,12 +437,12 @@
}
/**
- * Allow access Enums without enabling access to static methods
+ * Enum methods should also be banned alongside static methods
*/
public void testEnum() throws Exception {
- assertEquals("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
- assertEquals("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
- assertEquals("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
+ assertNull("ONE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[0]", String.class));
+ assertNull("TWO", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[1]", String.class));
+ assertNull("THREE", vs.findValue("@com.opensymphony.xwork2.ognl.MyNumbers@values()[2]", String.class));
}
public void testStaticMethodDisallow() {
diff --git a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
index 03bad82..381b7d0 100644
--- a/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
+++ b/core/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessTest.java
@@ -413,7 +413,7 @@
boolean actual = sma.isAccessible(context, MyValues.class, values, null);
// then
- assertTrue("Access to enums is blocked!", actual);
+ assertFalse("Access to enums is allowed!", actual);
}
@Test
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
index 8884667..54b2a96 100644
--- a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
+++ b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java
@@ -54,10 +54,12 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -588,6 +590,30 @@
assertEquals(Locale.CANADA_FRENCH, dispatcher.getLocale(request));
}
+ @Test
+ public void testExcludePatterns() {
+ initDispatcher(singletonMap(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json"));
+
+ assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
+ "/ns1/.*\\.json",
+ "/ns2/.*\\.json"
+ );
+ }
+
+ @Test
+ public void testExcludePatternsUsingCustomSeparator() {
+ Map<String, String> props = new HashMap<>();
+ props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
+ props.put(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
+
+ initDispatcher(props);
+
+ assertThat(dispatcher.getActionExcludedPatterns()).extracting(Pattern::toString).containsOnly(
+ "/ns1/[a-z]{1,10}.json",
+ "/ns2/[a-z]{1,10}.json"
+ );
+ }
+
public static Dispatcher spyDispatcherWithConfigurationManager(Dispatcher dispatcher, ConfigurationManager configurationManager) {
Dispatcher spiedDispatcher = spy(dispatcher);
doReturn(configurationManager).when(spiedDispatcher).createConfigurationManager(any());
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java b/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java
deleted file mode 100644
index aa2aaea..0000000
--- a/core/src/test/java/org/apache/struts2/dispatcher/InitOperationsTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.struts2.dispatcher;
-
-import com.opensymphony.xwork2.config.ConfigurationException;
-import com.opensymphony.xwork2.inject.ContainerBuilder;
-import com.opensymphony.xwork2.util.location.LocatableProperties;
-import org.apache.struts2.StrutsConstants;
-import org.apache.struts2.StrutsInternalTestCase;
-import org.apache.struts2.config.PropertiesConfigurationProvider;
-
-import java.util.List;
-import java.util.regex.Pattern;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class InitOperationsTest extends StrutsInternalTestCase {
-
- public void testExcludePatterns() {
- // given
- loadConfigurationProviders(new PropertiesConfigurationProvider() {
- @Override
- public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
- props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/.*\\.json,/ns2/.*\\.json");
- }
- });
-
- Dispatcher mockDispatcher = mock(Dispatcher.class);
- when(mockDispatcher.getContainer()).thenReturn(container);
-
- // when
- InitOperations init = new InitOperations();
- List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
-
- // then
- assertThat(patterns).extracting(Pattern::toString).containsOnly(
- "/ns1/.*\\.json",
- "/ns2/.*\\.json"
- );
- }
-
- public void testExcludePatternsUsingCustomSeparator() {
- // given
- loadConfigurationProviders(new PropertiesConfigurationProvider() {
- @Override
- public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
- props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN, "/ns1/[a-z]{1,10}.json///ns2/[a-z]{1,10}.json");
- props.setProperty(StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR, "//");
- }
- });
-
- Dispatcher mockDispatcher = mock(Dispatcher.class);
- when(mockDispatcher.getContainer()).thenReturn(container);
-
- // when
- InitOperations init = new InitOperations();
-
- String separator = container.getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN_SEPARATOR);
- List<Pattern> patterns = init.buildExcludedPatternsList(mockDispatcher);
-
- // then
- assertThat(separator).isNotBlank().isEqualTo("//");
- assertThat(patterns).extracting(Pattern::toString).containsOnly(
- "/ns1/[a-z]{1,10}.json",
- "/ns2/[a-z]{1,10}.json"
- );
- }
-}
diff --git a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
index eb87235..c7d5257 100644
--- a/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
+++ b/core/src/test/java/org/apache/struts2/dispatcher/StrutsPrepareAndExecuteFilterIntegrationTest.java
@@ -26,13 +26,10 @@
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
-import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.regex.Pattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -127,6 +124,7 @@
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
MockFilterConfig filterConfig = new MockFilterConfig();
+ filterConfig.addInitParameter("struts.action.excludePattern", ".*hello.*");
MockFilterChain filterChain = new MockFilterChain() {
@Override
public void doFilter(ServletRequest req, ServletResponse res) {
@@ -135,14 +133,7 @@
};
request.setRequestURI("/hello.action");
- StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter() {
- @Override
- public void init( FilterConfig filterConfig ) throws ServletException {
- super.init(filterConfig);
- excludedPatterns = new ArrayList<>();
- excludedPatterns.add(Pattern.compile(".*hello.*"));
- }
- };
+ StrutsPrepareAndExecuteFilter filter = new StrutsPrepareAndExecuteFilter();
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
assertEquals(200, response.getStatus());
diff --git a/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java b/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java
new file mode 100644
index 0000000..546d2fd
--- /dev/null
+++ b/core/src/test/java/org/apache/struts2/ognl/OgnlSetPossiblePropertyTest.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.ognl;
+
+import com.opensymphony.xwork2.XWorkTestCase;
+import com.opensymphony.xwork2.ognl.OgnlValueStack;
+import com.opensymphony.xwork2.util.ValueStackFactory;
+import ognl.OgnlRuntime;
+import org.apache.struts2.StrutsConstants;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotEquals;
+
+public class OgnlSetPossiblePropertyTest extends XWorkTestCase {
+ private OgnlValueStack vs;
+
+ public <T> T setUpClass(Class<T> holderClass) throws Exception {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, holderClass.getName() + "$ExcludedField");
+ loadButSet(properties);
+ vs = (OgnlValueStack) container.getInstance(ValueStackFactory.class).createValueStack();
+
+ T nonExcludedHolder = holderClass.getDeclaredConstructor().newInstance();
+ vs.push(nonExcludedHolder);
+
+ return nonExcludedHolder;
+ }
+
+ public void testSetFieldValueDontAssignWhenHolderClassAndFieldClassHaveOnlyPublicFields() throws Exception {
+ /* Case: to test setFieldValue without having set method
+ *
+ * NonExcludedHolder class
+ * - field: public
+ * ExcludeField class
+ * - field: public
+ */
+ HolderWithPublicField holder = setUpClass(HolderWithPublicField.class);
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+ }
+
+ public void testSetMethodValueDontAssignWhenHolderAndFieldClassWithPublicMethodsAndPrivateFields() throws Exception {
+ /* Case: to test setMethodValue, so to make fields as private
+ *
+ * NonExcludedHolder class
+ * - field: private
+ * - method: public
+ * ExcludeField class
+ * - field: private
+ * - method: public
+ */
+ HolderWithPublicMethod holder = setUpClass(HolderWithPublicMethod.class);
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+ }
+
+ public void testSetFieldValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicField() throws Exception {
+ /* Case: to test setFieldValue when holder get method is public and field class set method is private so fallback to set field
+ *
+ * NonExcludedHolder class
+ * - field: private
+ * - method: public
+ * ExcludeField class
+ * - field: public
+ * - method: private
+ */
+ HolderWhoseFieldWithPrivateMethod holder = setUpClass(HolderWhoseFieldWithPrivateMethod.class);
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+ }
+
+ public void testSetMethodValueDontAssignWhenHolderClassWithGetMethodAndFieldClassWithPublicMethod() throws Exception {
+ /* Case: to test setMethodValue when holder get method is public and field class field is private so only call to set method
+ *
+ * NonExcludedHolder class
+ * - field: private
+ * - method: public
+ * ExcludeField class
+ * - field: private
+ * - method: public
+ */
+ HolderWhoseFieldWithPublicMethod holder = setUpClass(HolderWhoseFieldWithPublicMethod.class);
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+ }
+
+ public void testWriteMethodValueDontAssignWhenWriteMethodIsNotAccessible() throws Exception {
+ /* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is private so fallback to write method
+ *
+ * NonExcludedHolder class
+ * - field: private
+ * - method: public
+ * ExcludeField class
+ * - field: private
+ * - set method: private
+ * - write method: public
+ */
+ HolderWhoseFieldWithPublicWriteMethod holder = setUpClass(HolderWhoseFieldWithPublicWriteMethod.class);
+ Method writeMethod = OgnlRuntime.getWriteMethod(HolderWhoseFieldWithPublicWriteMethod.ExcludedField.class, "excludedFieldString");
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertEquals("setexcludedfieldstring", writeMethod.getName());
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldString);
+ }
+
+ public void testWriteMethodValueDontAssignWhenPublicSetterDifferentFieldName() throws Exception {
+ /* Case: to test invoke method from getWriteMethod when holder get method is public and field class field / set method is of different name
+ *
+ * NonExcludedHolder class
+ * - field: private
+ * - method: public
+ * ExcludeField class
+ * - field: private
+ * - set method: public (but not matching with field name)
+ */
+ HolderWhoseFieldWithPublicSetterDifferentFieldName holder = setUpClass(HolderWhoseFieldWithPublicSetterDifferentFieldName.class);
+ vs.setValue("excludedField.excludedFieldString", "EXPLOITED");
+
+ assertNotEquals("EXPLOITED", holder.excludedField.excludedFieldStringInternal);
+ }
+
+
+ public static class HolderWithPublicField {
+ public ExcludedField excludedField = new ExcludedField();
+
+ public static class ExcludedField {
+ public String excludedFieldString = "defaultValue";
+ }
+ }
+
+ public static class HolderWithPublicMethod {
+ private ExcludedField excludedField = new ExcludedField();
+
+ public ExcludedField getExcludedField() {
+ return excludedField;
+ }
+
+ public static class ExcludedField {
+ private String excludedFieldString = "defaultValue";
+
+ public void setExcludedFieldString(String value) {
+ this.excludedFieldString = value;
+ }
+ }
+ }
+
+ public static class HolderWhoseFieldWithPrivateMethod {
+ private ExcludedField excludedField = new ExcludedField();
+
+
+ public ExcludedField getExcludedField() {
+ return excludedField;
+ }
+
+ public static class ExcludedField {
+ public String excludedFieldString = "defaultValue";
+
+ private void setExcludedFieldString(String value) {
+ this.excludedFieldString = value;
+ }
+ }
+ }
+
+ public static class HolderWhoseFieldWithPublicMethod {
+ private ExcludedField excludedField = new ExcludedField();
+
+
+ public ExcludedField getExcludedField() {
+ return excludedField;
+ }
+
+ public static class ExcludedField {
+ public String excludedFieldString = "defaultValue";
+
+ private void setExcludedFieldString(String value) {
+ this.excludedFieldString = value;
+ }
+ }
+ }
+
+ public static class HolderWhoseFieldWithPublicWriteMethod {
+ private ExcludedField excludedField = new ExcludedField();
+
+
+ public ExcludedField getExcludedField() {
+ return excludedField;
+ }
+
+ public static class ExcludedField {
+ private String excludedFieldString = "defaultValue";
+
+ private void setExcludedFieldString(String value) {
+ this.excludedFieldString = value;
+ }
+
+ public void setexcludedfieldstring(String value) {
+ this.excludedFieldString = value;
+ }
+ }
+ }
+
+ public static class HolderWhoseFieldWithPublicSetterDifferentFieldName {
+ private ExcludedField excludedField = new ExcludedField();
+
+ public ExcludedField getExcludedField() {
+ return excludedField;
+ }
+
+ public static class ExcludedField {
+ private String excludedFieldStringInternal = "defaultValue";
+
+ public void setExcludedFieldString(String value) {
+ this.excludedFieldStringInternal = value;
+ }
+ }
+ }
+}
diff --git a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
index 7f4b545..b7db515 100644
--- a/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
+++ b/core/src/test/java/org/apache/struts2/views/jsp/ui/DebugTagTest.java
@@ -217,23 +217,9 @@
/**
* Overwrite the Struts Constant and reload container
*/
- private void setStrutsConstant(final Map<String, String> overwritePropeties) {
- configurationManager.addContainerProvider(new StubConfigurationProvider() {
- @Override
- public boolean needsReload() {
- return true;
- }
-
- @Override
- public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
- for (Map.Entry<String, String> stringStringEntry : overwritePropeties.entrySet()) {
- props.setProperty(stringStringEntry.getKey(), stringStringEntry.getValue(), null);
- }
- }
- });
-
- configurationManager.reload();
- container = configurationManager.getConfiguration().getContainer();
+ @Override
+ protected void setStrutsConstant(final Map<String, String> overwritePropeties) {
+ super.setStrutsConstant(overwritePropeties);
stack.getActionContext().withContainer(container);
}
-}
\ No newline at end of file
+}
diff --git a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml
new file mode 100644
index 0000000..4e42422
--- /dev/null
+++ b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-extends-final.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+ "struts-6.5.dtd">
+<struts>
+ <package name="default" namespace="/default">
+ <action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+
+ <package name="parentLevelTwo" namespace="/parent2" final="true">
+ <action name="levelTwo" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+
+ <package name="child" namespace="/child" extends="default,parentLevelTwo">
+ <action name="single" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+</struts>
diff --git a/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml
new file mode 100644
index 0000000..2a49a15
--- /dev/null
+++ b/core/src/test/resources/com/opensymphony/xwork2/config/providers/xwork-test-package-final.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<!DOCTYPE struts PUBLIC
+ "-//Apache Software Foundation//DTD Struts Configuration 6.5//EN"
+ "struts-6.5.dtd">
+<struts>
+ <package name="default" namespace="/default">
+ <action name="default" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+
+ <package name="finalPackage" namespace="/final" extends="default" final="true">
+ <action name="actionFinal" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+
+ <package name="normalPackage" namespace="/normal" extends="default">
+ <action name="actionNormal" class="com.opensymphony.xwork2.ActionSupport"/>
+ </package>
+</struts>
diff --git a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
index 4d8046d..3838ca9 100644
--- a/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
+++ b/plugins/spring/src/test/java/com/opensymphony/xwork2/ognl/SecurityMemberAccessProxyTest.java
@@ -29,6 +29,11 @@
public class SecurityMemberAccessProxyTest extends XWorkTestCase {
private Map<String, Object> context;
+ private ActionProxy proxy;
+ private Map<String, Member> members;
+ private final SecurityMemberAccess sma = new SecurityMemberAccess(true);
+ private final String PROXY_MEMBER_METHOD = "isExposeProxy";
+ private final String TEST_SUB_BEAN_CLASS_METHOD = "setIssueId";
@Override
public void setUp() throws Exception {
@@ -39,30 +44,51 @@
XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml");
container.inject(provider);
loadConfigurationProviders(provider);
+
+ // Setup proxy object
+ setupProxy();
}
public void testProxyAccessIsBlocked() throws Exception {
- ActionProxy proxy = actionProxyFactory.createActionProxy(null,
- "chaintoAOPedTestSubBeanAction", null, context);
+ members.values().forEach(member -> {
+ // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to false, the proxy access is blocked
+ sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
+ assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
- SecurityMemberAccess sma = new SecurityMemberAccess(true);
+ // When disallowProxyObjectAccess is set to true, and disallowProxyMemberAccess is set to true, the proxy access is blocked
+ sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
+ assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
+ });
+
+ // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the proxy member access is blocked
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
-
- Member member = proxy.getAction().getClass().getMethod("isExposeProxy");
-
- boolean accessible = sma.isAccessible(context, proxy.getAction(), member, "");
- assertFalse(accessible);
+ assertFalse(sma.isAccessible(context, proxy.getAction(), members.get(PROXY_MEMBER_METHOD), ""));
}
public void testProxyAccessIsAccessible() throws Exception {
- ActionProxy proxy = actionProxyFactory.createActionProxy(null,
- "chaintoAOPedTestSubBeanAction", null, context);
+ members.values().forEach(member -> {
+ // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to false, the proxy access is allowed
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
+ assertTrue(sma.isAccessible(context, proxy.getAction(), member, ""));
+ });
- SecurityMemberAccess sma = new SecurityMemberAccess(true);
+ // When disallowProxyObjectAccess is set to false, and disallowProxyMemberAccess is set to true, the original class member access is allowed
+ sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
+ sma.useDisallowProxyMemberAccess(Boolean.TRUE.toString());
+ assertTrue(sma.isAccessible(context, proxy.getAction(), members.get(TEST_SUB_BEAN_CLASS_METHOD), ""));
+ }
- Member member = proxy.getAction().getClass().getMethod("isExposeProxy");
+ private void setupProxy() throws NoSuchMethodException {
+ proxy = actionProxyFactory.createActionProxy(null, "chaintoAOPedTestSubBeanAction", null, context);
- boolean accessible = sma.isAccessible(context, proxy.getAction(), member, "");
- assertTrue(accessible);
+ members = new HashMap<>();
+ // method is proxy member
+ members.put(PROXY_MEMBER_METHOD, proxy.getAction().getClass().getMethod(PROXY_MEMBER_METHOD));
+ // method is not proxy member but from POJO class
+ members.put(TEST_SUB_BEAN_CLASS_METHOD, proxy.getAction().getClass().getMethod(TEST_SUB_BEAN_CLASS_METHOD, String.class));
}
}
diff --git a/plugins/tiles/pom.xml b/plugins/tiles/pom.xml
index 618bc6a..208e50f 100644
--- a/plugins/tiles/pom.xml
+++ b/plugins/tiles/pom.xml
@@ -40,7 +40,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
- <version>3.1.0</version>
+ <version>3.2.0</version>
<executions>
<execution>
<phase>compile</phase>
diff --git a/pom.xml b/pom.xml
index 34490d5..4ad0d73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,10 +109,10 @@
<maven.compiler.target>1.8</maven.compiler.target>
<!-- dependency versions in alphanumeric order -->
- <asm.version>9.6</asm.version>
+ <asm.version>9.7</asm.version>
<jackson.version>2.16.1</jackson.version>
<log4j2.version>2.23.1</log4j2.version>
- <ognl.version>3.3.4</ognl.version>
+ <ognl.version>3.3.5</ognl.version>
<slf4j.version>2.0.12</slf4j.version>
<spring.platformVersion>5.3.31</spring.platformVersion>
<tiles.version>3.0.8</tiles.version>
@@ -487,7 +487,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
- <version>2.16.1</version>
+ <version>2.16.2</version>
<reportSets>
<reportSet>
<reports>
@@ -978,7 +978,7 @@
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
- <version>3.25.2</version>
+ <version>3.25.3</version>
<scope>test</scope>
</dependency>