mirror of
https://github.com/apache/struts.git
synced 2026-08-06 15:17:00 +00:00
Merge remote-tracking branch 'origin/master' into merge-master-to-70-2024-04-20
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+16
-9
@@ -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 —
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+8
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
+56
-12
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-11
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
@@ -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>
|
||||
+37
@@ -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>
|
||||
@@ -43,7 +43,7 @@
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>3.0.2</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+40
-14
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user