Merge remote-tracking branch 'origin/master' into merge-master-to-70-2024-04-20

This commit is contained in:
Kusal Kithul-Godage
2024-04-20 20:48:47 +10:00
35 changed files with 897 additions and 231 deletions
+2
View File
@@ -104,7 +104,9 @@
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>make-assembly</id>
+9
View File
@@ -185,6 +185,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>
@@ -36,6 +36,8 @@ import org.apache.commons.lang3.ClassUtils;
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 @@ public abstract class XWorkTestCase extends TestCase {
@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 @@ public abstract class XWorkTestCase extends TestCase {
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 @@ public abstract class XWorkTestCase extends TestCase {
.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);
}
}
@@ -47,6 +47,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
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 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
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 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
return isAbstract;
}
public boolean isFinal() {
return isFinal;
}
public Map<String, ActionConfig> getActionConfigs() {
return actionConfigs;
}
@@ -360,6 +366,7 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
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 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
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 @@ public class PackageConfig extends Located implements Comparable<PackageConfig>,
return this;
}
public Builder isFinal(boolean isFinal) {
target.isFinal = isFinal;
return this;
}
public Builder defaultInterceptorRef(String name) {
target.defaultInterceptorRef = name;
return this;
@@ -120,6 +120,7 @@ import java.util.HashMap;
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 @@ public class DefaultConfiguration implements Configuration {
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 @@ public class DefaultConfiguration implements Configuration {
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 @@ public class DefaultConfiguration implements Configuration {
}
// 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) {
@@ -603,8 +603,8 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
*/
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 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
PackageConfig.Builder cfg = new PackageConfig.Builder(name)
.namespace(namespace)
.isAbstract(isAbstract)
.isFinal(isFinal)
.strictMethodInvocation(strictDMI)
.location(DomHelper.getLocationObject(packageElement));
@@ -627,17 +628,23 @@ public abstract class XmlDocConfigurationProvider implements ConfigurationProvid
// 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()) {
@@ -32,6 +32,7 @@ public class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key
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 class DefaultOgnlCacheFactory<Key, Value> implements OgnlCacheFactory<Key
}
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
@@ -31,7 +31,6 @@ import org.apache.struts2.ognl.ThreadAllowlist;
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 @@ public class SecurityMemberAccess implements MemberAccess {
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 @@ public class SecurityMemberAccess implements MemberAccess {
}
}
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 @@ public class SecurityMemberAccess implements MemberAccess {
}
/**
* @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 @@ public class SecurityMemberAccess implements MemberAccess {
* @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 @@ public class SecurityMemberAccess implements MemberAccess {
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 @@ public class SecurityMemberAccess implements MemberAccess {
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);
@@ -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 @@ public class ProxyUtil {
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 @@ public class ProxyUtil {
return flag;
}
boolean isProxy = isSpringAopProxy(object);
boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
isProxyCache.put(clazz, isProxy);
return isProxy;
@@ -87,7 +96,7 @@ public class ProxyUtil {
* @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,12 +105,40 @@ public class ProxyUtil {
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 &mdash;
@@ -237,6 +237,8 @@ public final class StrutsConstants {
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";
@@ -487,6 +489,7 @@ public final class StrutsConstants {
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";
@@ -54,6 +54,7 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
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;
@@ -90,6 +90,7 @@ public class ConstantConfig {
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 @@ public class ConstantConfig {
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 @@ public class ConstantConfig {
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 @@ public class ConstantConfig {
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 @@ public class ConstantConfig {
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 @@ public class ConstantConfig {
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;
}
@@ -78,6 +78,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.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.Set;
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 @@ public class Dispatcher {
*/
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 @@ public class Dispatcher {
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;
@@ -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 @@ public class InitOperations {
* @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();
}
}
@@ -223,21 +223,23 @@ public class PrepareOperations {
* 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());
}
/**
@@ -50,6 +50,12 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
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 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
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 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
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 {
@@ -43,6 +43,12 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
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 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
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 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
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);
@@ -214,6 +214,9 @@ struts.xslt.nocache=false
### 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
+158
View File
@@ -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 -->
@@ -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,
@@ -31,6 +31,7 @@ import com.opensymphony.xwork2.inject.ContainerBuilder;
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 @@ public class ConfigurationTest extends XWorkTestCase {
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() {
@@ -36,10 +36,9 @@ import java.util.List;
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 class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
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 class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
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 class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
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 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
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 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
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 class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
}
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 @@ public class XmlConfigurationProviderPackagesTest extends ConfigurationTestBase
assertEquals(expectedDefaultClassRefPackage, configuration.getPackageConfig(hasDefaultClassRefPkgName));
assertEquals(expectedNoDefaultClassRefPackage, configuration.getPackageConfig(noDefaultClassRefPkgName));
}
private String getXmlConfigFilePath(String fileName) {
return "com/opensymphony/xwork2/config/providers/" + fileName;
}
}
@@ -437,12 +437,12 @@ public class OgnlValueStackTest extends XWorkTestCase {
}
/**
* 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() {
@@ -413,7 +413,7 @@ public class SecurityMemberAccessTest {
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
@@ -55,10 +55,12 @@ import java.util.HashSet;
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;
@@ -589,6 +591,30 @@ public class DispatcherTest extends StrutsJUnit4InternalTestCase {
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());
@@ -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"
);
}
}
@@ -26,13 +26,10 @@ import org.springframework.mock.web.MockFilterConfig;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.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 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest {
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 @@ public class StrutsPrepareAndExecuteFilterIntegrationTest {
};
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());
@@ -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;
}
}
}
}
@@ -217,23 +217,9 @@ public class DebugTagTest extends AbstractUITagTest {
/**
* 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);
}
}
}
@@ -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>
@@ -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>
+1 -1
View File
@@ -43,7 +43,7 @@
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
+2 -2
View File
@@ -66,13 +66,13 @@
<artifactId>commons-beanutils</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mockobjects</groupId>
<artifactId>mockobjects-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
@@ -29,6 +29,11 @@ import java.util.Map;
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 @@ public class SecurityMemberAccessProxyTest extends XWorkTestCase {
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));
}
}
+1 -1
View File
@@ -118,7 +118,7 @@
<log4j2.version>2.23.1</log4j2.version>
<maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
<mockito.version>5.8.0</mockito.version>
<ognl.version>3.3.4</ognl.version>
<ognl.version>3.3.5</ognl.version>
<sitemesh.version>2.5.0</sitemesh.version>
<slf4j.version>2.0.12</slf4j.version>
<spring.platformVersion>6.0.13</spring.platformVersion>