mirror of
https://github.com/apache/struts.git
synced 2026-08-10 00:57:04 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbfb59cb7c | |||
| d4a0f4fdde | |||
| 9fe1a4a031 | |||
| 3693ea2eec | |||
| 7cb57087d3 | |||
| 398e104fe0 | |||
| 7f57e895a1 | |||
| 81b4943176 | |||
| 05680d7827 | |||
| 8555dc266e | |||
| c6f394a0e8 | |||
| c965812ffe | |||
| abf03fdccc | |||
| 2f814186c8 | |||
| 7762d7fba2 | |||
| 82b364d521 | |||
| 98f2e68e0b | |||
| 40ab7a776a | |||
| 8b22f7170f | |||
| 688413a359 | |||
| 53ed5f6dfa | |||
| 100ef07a44 | |||
| 4267bf0647 | |||
| 898a8d9ef9 | |||
| 75ebbf4367 | |||
| a8954508c6 | |||
| ba46c18f07 | |||
| b96cf2c072 | |||
| 13916c8b84 | |||
| a99162a1a4 | |||
| b07268d5bd | |||
| 444e4d4c07 | |||
| cf34f0dbc0 | |||
| cabc076e48 | |||
| de2a8d9902 | |||
| 9133d24f69 | |||
| 86b14c7062 | |||
| 54bf309f88 | |||
| 63fcf0f14f | |||
| 03db4f1ab0 | |||
| 4a05653a55 | |||
| 3a6ad5a557 | |||
| dcaff0bf10 | |||
| a25173f65d | |||
| 0cdce05745 | |||
| ee040babd4 | |||
| f6b25ac8aa | |||
| 5c2be7cc7d | |||
| e5fcc8f175 | |||
| 855b95e557 | |||
| 00752e33c4 | |||
| 4a8ff99b1c | |||
| 9682b3b429 | |||
| 8268d0b063 | |||
| 36230dc5be | |||
| 9ee39d0a2c | |||
| ab89b8287a | |||
| 19252adea0 | |||
| 40ccc74b8a | |||
| d147543dcd | |||
| 49eda37f2d | |||
| 0a57cac6d6 | |||
| 195b0e59d0 | |||
| 03126c01bf | |||
| 73be382a96 | |||
| f338fda6d8 | |||
| 7c523ac33e | |||
| 039c5f8d42 | |||
| 6e12c25ac0 | |||
| c6b53d4887 | |||
| 942fbd67e7 | |||
| b36e88ff4b | |||
| 4dfbe09343 | |||
| 649760d8b0 | |||
| 49b8eedf1f | |||
| c0d1217d7c | |||
| f2e93d017b | |||
| 3ed212a1f2 | |||
| ed3786c6b0 | |||
| 5cd9356049 | |||
| 2d9af47a90 | |||
| c1c6a463a8 | |||
| 36275b9e04 | |||
| a88cb741d0 | |||
| 3cde668598 | |||
| eca5b21088 | |||
| 856d98010a | |||
| b2b35f066c | |||
| efdaf7e50f | |||
| 3c21e82290 | |||
| f5cfb88f1f | |||
| 63267a8e06 | |||
| 62b4b65c4c | |||
| 4310536799 | |||
| ad49ea866a | |||
| 7e9f6e84f2 | |||
| 100f5052d4 | |||
| 0a720971c7 | |||
| 6ac8b04ad2 | |||
| 1562e66a89 | |||
| 85783a0cc7 | |||
| 0151bdeb9c | |||
| 0aa2f269f2 | |||
| 4088f2ee2b | |||
| 1d51d00ecb | |||
| 929a60184b | |||
| bf5f29132b | |||
| 758174c52e | |||
| f9f6327570 | |||
| ed0c7287d9 | |||
| 1a6e2fef7a | |||
| 5f717cdb89 | |||
| e2ec114577 | |||
| 0074b70286 | |||
| efa20426c9 | |||
| 268ed789ee | |||
| ccd5f5d534 | |||
| ac409d6cba | |||
| c6d13f1016 |
@@ -18,8 +18,9 @@ github:
|
||||
contexts:
|
||||
- build
|
||||
required_pull_request_reviews:
|
||||
require_code_owner_reviews: true
|
||||
required_approving_review_count: 1
|
||||
# it does not work because our github teams are private/secret, see INFRA-25666
|
||||
require_code_owner_reviews: false
|
||||
required_approving_review_count: 0
|
||||
autolink_jira:
|
||||
- WW
|
||||
dependabot_alerts: true
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # 2.3.1
|
||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # 2.3.3
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
publish_results: true
|
||||
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # 4.3.1
|
||||
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # 4.3.4
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-showcase</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
<name>Struts 2 Rest Showcase Webapp</name>
|
||||
<description>Struts 2 Rest Showcase Example</description>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-apps</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-showcase</artifactId>
|
||||
@@ -157,7 +157,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>3.0.0-M6</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>it.org.apache.struts2.showcase.*Test</include>
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-assembly</artifactId>
|
||||
@@ -106,7 +106,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<version>3.7.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
|
||||
+4
-4
@@ -25,11 +25,11 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-bom</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>Struts 2 Bill of Materials</name>
|
||||
@@ -44,7 +44,7 @@
|
||||
</licenses>
|
||||
|
||||
<properties>
|
||||
<struts-version.version>6.4.0</struts-version.version>
|
||||
<struts-version.version>6.5.0</struts-version.version>
|
||||
<maven.site.skip>true</maven.site.skip>
|
||||
<maven.site.deploy.skip>true</maven.site.deploy.skip>
|
||||
</properties>
|
||||
@@ -190,7 +190,7 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<scm>
|
||||
<tag>STRUTS_6_4_0</tag>
|
||||
<tag>STRUTS_6_5_0</tag>
|
||||
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
|
||||
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
|
||||
<url>https://github.com/apache/struts/</url>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-admin-bundle</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-demo-bundle</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-bundles</artifactId>
|
||||
|
||||
+16
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
<artifactId>struts2-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -230,6 +230,15 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Optional used in com.opensymphony.xwork2.util.ProxyUtil to detect if object is HibernateProxy -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
<version>5.6.15.Final</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
@@ -251,6 +260,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
||||
@@ -90,6 +90,11 @@ public class ActionSupport implements Action, Validateable, ValidationAware, Tex
|
||||
return getLocaleProvider().isValidLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale toLocale(String localeStr) {
|
||||
return getLocaleProvider().toLocale(localeStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey(String key) {
|
||||
return getTextProvider().hasKey(key);
|
||||
|
||||
@@ -46,17 +46,23 @@ public class DefaultLocaleProvider implements LocaleProvider {
|
||||
|
||||
@Override
|
||||
public boolean isValidLocaleString(String localeStr) {
|
||||
Locale locale = this.toLocale(localeStr);
|
||||
return isValidLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidLocale(Locale locale) {
|
||||
return locale != null && LocaleUtils.isAvailableLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale toLocale(String localeStr) {
|
||||
Locale locale = null;
|
||||
try {
|
||||
locale = LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn(new ParameterizedMessage("Cannot convert [{}] to proper locale", localeStr), e);
|
||||
}
|
||||
return isValidLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidLocale(Locale locale) {
|
||||
return LocaleUtils.isAvailableLocale(locale);
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
*/
|
||||
package com.opensymphony.xwork2;
|
||||
|
||||
import org.apache.commons.lang3.LocaleUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
@@ -58,4 +61,17 @@ public interface LocaleProvider {
|
||||
*/
|
||||
boolean isValidLocale(Locale locale);
|
||||
|
||||
/**
|
||||
* Tries to convert provided locale string into {@link Locale} or returns null
|
||||
* @param localeStr a String representing locale, e.g.: en_EN
|
||||
* @return instance of {@link Locale} or null
|
||||
* @since Struts 6.5.0
|
||||
*/
|
||||
default Locale toLocale(String localeStr) {
|
||||
try {
|
||||
return LocaleUtils.toLocale(StringUtils.trimToNull(localeStr));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ public class EnvsValueSubstitutor implements ValueSubstitutor {
|
||||
public String substitute(String value) {
|
||||
LOG.debug("Substituting value {} with proper System variable or environment variable", value);
|
||||
|
||||
String substituted = sysStrSubstitutor.replace(value);
|
||||
return envStrSubstitutor.replace(substituted);
|
||||
String substituted = envStrSubstitutor.replace(value);
|
||||
return sysStrSubstitutor.replace(substituted);
|
||||
}
|
||||
}
|
||||
|
||||
+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()) {
|
||||
|
||||
@@ -25,8 +25,6 @@ import java.util.Map;
|
||||
/**
|
||||
* ValidationAware classes can accept Action (class level) or field level error messages. Action level messages are kept
|
||||
* in a Collection. Field level error messages are kept in a Map from String field name to a List of field error msgs.
|
||||
*
|
||||
* @author plightbo
|
||||
*/
|
||||
public interface ValidationAware {
|
||||
|
||||
@@ -119,7 +117,9 @@ public interface ValidationAware {
|
||||
*
|
||||
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
|
||||
*/
|
||||
boolean hasErrors();
|
||||
default boolean hasErrors() {
|
||||
return hasActionErrors() || hasFieldErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there are any field errors associated with this action.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ErrorMessageBuilder {
|
||||
}
|
||||
|
||||
public ErrorMessageBuilder errorSettingExpressionWithValue(String expr, Object value) {
|
||||
appenExpression(expr);
|
||||
appendExpression(expr);
|
||||
if (value instanceof Object[]) {
|
||||
appendValueAsArray((Object[]) value, message);
|
||||
} else {
|
||||
@@ -42,7 +42,7 @@ public class ErrorMessageBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void appenExpression(String expr) {
|
||||
private void appendExpression(String expr) {
|
||||
message.append("Error setting expression '");
|
||||
message.append(expr);
|
||||
message.append("' with value ");
|
||||
|
||||
@@ -47,7 +47,6 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.opensymphony.xwork2.util.ConfigParseUtil.toClassesSet;
|
||||
@@ -68,9 +67,6 @@ public class OgnlUtil {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(OgnlUtil.class);
|
||||
|
||||
// Flag used to reduce flooding logs with WARNs about using DevMode excluded packages
|
||||
private final AtomicBoolean warnReported = new AtomicBoolean(false);
|
||||
|
||||
private final OgnlCache<String, Object> expressionCache;
|
||||
private final OgnlCache<Class<?>, BeanInfo> beanInfoCache;
|
||||
private TypeConverter defaultConverter;
|
||||
@@ -80,11 +76,6 @@ public class OgnlUtil {
|
||||
private boolean enableExpressionCache = true;
|
||||
private boolean enableEvalExpression;
|
||||
|
||||
private String devModeExcludedClasses = "";
|
||||
private String devModeExcludedPackageNamePatterns = "";
|
||||
private String devModeExcludedPackageNames = "";
|
||||
private String devModeExcludedPackageExemptClasses = "";
|
||||
|
||||
private Container container;
|
||||
|
||||
/**
|
||||
@@ -164,9 +155,12 @@ public class OgnlUtil {
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
|
||||
/**
|
||||
* @deprecated since 6.5.0, no replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void setDevModeExcludedClasses(String commaDelimitedClasses) {
|
||||
this.devModeExcludedClasses = commaDelimitedClasses;
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,9 +171,12 @@ public class OgnlUtil {
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
|
||||
/**
|
||||
* @deprecated since 6.5.0, no replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void setDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
|
||||
this.devModeExcludedPackageNamePatterns = commaDelimitedPackagePatterns;
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,9 +187,12 @@ public class OgnlUtil {
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
|
||||
/**
|
||||
* @deprecated since 6.5.0, no replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
protected void setDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
|
||||
this.devModeExcludedPackageNames = commaDelimitedPackageNames;
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,9 +203,12 @@ public class OgnlUtil {
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
|
||||
/**
|
||||
* @deprecated since 6.5.0, no replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
|
||||
this.devModeExcludedPackageExemptClasses = commaDelimitedClasses;
|
||||
// Must be set directly on SecurityMemberAccess
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -856,6 +859,11 @@ public class OgnlUtil {
|
||||
return createDefaultContext(root, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that the allowlist capability is not enforced by the {@link OgnlContext} returned by this method. Currently,
|
||||
* this context is only leveraged by some public methods on {@link OgnlUtil} which are called by
|
||||
* {@link OgnlReflectionProvider}.
|
||||
*/
|
||||
protected Map<String, Object> createDefaultContext(Object root, ClassResolver resolver) {
|
||||
if (resolver == null) {
|
||||
resolver = container.getInstance(RootAccessor.class);
|
||||
@@ -867,17 +875,6 @@ public class OgnlUtil {
|
||||
SecurityMemberAccess memberAccess = container.getInstance(SecurityMemberAccess.class);
|
||||
memberAccess.useEnforceAllowlistEnabled(Boolean.FALSE.toString());
|
||||
|
||||
if (devMode) {
|
||||
if (!warnReported.get()) {
|
||||
warnReported.set(true);
|
||||
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
|
||||
}
|
||||
memberAccess.useExcludedClasses(devModeExcludedClasses);
|
||||
memberAccess.useExcludedPackageNamePatterns(devModeExcludedPackageNamePatterns);
|
||||
memberAccess.useExcludedPackageNames(devModeExcludedPackageNames);
|
||||
memberAccess.useExcludedPackageExemptClasses(devModeExcludedPackageExemptClasses);
|
||||
}
|
||||
|
||||
return Ognl.createDefaultContext(root, memberAccess, resolver, defaultConverter);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ import org.apache.struts2.ognl.ProviderAllowlist;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
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.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -51,6 +51,8 @@ import static java.text.MessageFormat.format;
|
||||
import static java.util.Collections.emptySet;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_CLASSES;
|
||||
import static org.apache.struts2.StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES;
|
||||
|
||||
/**
|
||||
* Allows access decisions to be made on the basis of whether a member is static or not.
|
||||
@@ -77,16 +79,29 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
|
||||
private final ProviderAllowlist providerAllowlist;
|
||||
private final ThreadAllowlist threadAllowlist;
|
||||
|
||||
private boolean allowStaticFieldAccess = true;
|
||||
|
||||
private Set<Pattern> excludeProperties = emptySet();
|
||||
private Set<Pattern> acceptProperties = emptySet();
|
||||
|
||||
private Set<String> excludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
|
||||
private Set<Pattern> excludedPackageNamePatterns = emptySet();
|
||||
private Set<String> excludedPackageNames = emptySet();
|
||||
private Set<String> excludedPackageExemptClasses = emptySet();
|
||||
|
||||
private volatile boolean isDevModeInit;
|
||||
private boolean isDevMode;
|
||||
private Set<String> devModeExcludedClasses = unmodifiableSet(new HashSet<>(singletonList(Object.class.getName())));
|
||||
private Set<Pattern> devModeExcludedPackageNamePatterns = emptySet();
|
||||
private Set<String> devModeExcludedPackageNames = emptySet();
|
||||
private Set<String> devModeExcludedPackageExemptClasses = emptySet();
|
||||
|
||||
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;
|
||||
|
||||
@@ -147,11 +162,11 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
if (target != null) {
|
||||
// Special case: Target is a Class object but not Class.class
|
||||
if (Class.class.equals(target.getClass()) && !Class.class.equals(target)) {
|
||||
if (!isStatic(member)) {
|
||||
throw new IllegalArgumentException("Member expected to be static!");
|
||||
if (!isStatic(member) && !Constructor.class.equals(member.getClass())) {
|
||||
throw new IllegalArgumentException("Member expected to be static or constructor!");
|
||||
}
|
||||
if (!member.getDeclaringClass().equals(target)) {
|
||||
throw new IllegalArgumentException("Target class does not match static member!");
|
||||
throw new IllegalArgumentException("Target class does not match member!");
|
||||
}
|
||||
target = null; // This information is not useful to us and conflicts with following logic which expects target to be null or an instance containing the member
|
||||
// Standard case: Member should exist on target
|
||||
@@ -160,6 +175,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;
|
||||
@@ -203,12 +223,27 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
* @return {@code true} if member access is allowed
|
||||
*/
|
||||
protected boolean checkAllowlist(Object target, Member member) {
|
||||
Class<?> memberClass = member.getDeclaringClass();
|
||||
if (!enforceAllowlistEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!disallowProxyObjectAccess && target != null && ProxyUtil.isProxy(target)) {
|
||||
// If `disallowProxyObjectAccess` is not set, allow resolving Hibernate entities to their underlying
|
||||
// classes/members. This allows the allowlist capability to continue working and offer some level of
|
||||
// protection in applications where the developer has accepted the risk of allowing OGNL access to Hibernate
|
||||
// entities. This is preferred to having to disable the allowlist capability entirely.
|
||||
Object newTarget = ProxyUtil.getHibernateProxyTarget(target);
|
||||
if (newTarget != target) {
|
||||
logAllowlistHibernateEntity(target, newTarget);
|
||||
target = newTarget;
|
||||
member = ProxyUtil.resolveTargetMember(member, newTarget);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> memberClass = member.getDeclaringClass();
|
||||
if (!isClassAllowlisted(memberClass)) {
|
||||
LOG.warn(format("Declaring class [{0}] of member type [{1}] is not allowlisted!", memberClass, member));
|
||||
LOG.warn("Declaring class [{}] of member type [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
|
||||
memberClass, member, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
|
||||
return false;
|
||||
}
|
||||
if (target == null || target.getClass() == memberClass) {
|
||||
@@ -216,12 +251,27 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
}
|
||||
Class<?> targetClass = target.getClass();
|
||||
if (!isClassAllowlisted(targetClass)) {
|
||||
LOG.warn(format("Target class [{0}] of target [{1}] is not allowlisted!", targetClass, target));
|
||||
LOG.warn("Target class [{}] of target [{}] is not allowlisted! Add to '{}' or '{}' configuration.",
|
||||
targetClass, target, STRUTS_ALLOWLIST_CLASSES, STRUTS_ALLOWLIST_PACKAGE_NAMES);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void logAllowlistHibernateEntity(Object original, Object resolved) {
|
||||
if (!isDevMode && !LOG.isDebugEnabled()) {
|
||||
return;
|
||||
}
|
||||
String msg = "Hibernate entity [{}] resolved to [{}] for purpose of OGNL allowlisting." +
|
||||
" We don't recommend executing OGNL expressions against Hibernate entities, you may disallow this behaviour using the configuration `{}=true`.";
|
||||
Object[] args = {original, resolved, StrutsConstants.STRUTS_DISALLOW_PROXY_OBJECT_ACCESS};
|
||||
if (isDevMode) {
|
||||
LOG.warn(msg, args);
|
||||
} else {
|
||||
LOG.debug(msg, args);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isClassAllowlisted(Class<?> clazz) {
|
||||
return allowlistClasses.contains(clazz)
|
||||
|| ALLOWLIST_REQUIRED_CLASSES.contains(clazz)
|
||||
@@ -235,6 +285,7 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
* @return {@code true} if member access is allowed
|
||||
*/
|
||||
protected boolean checkExclusionList(Object target, Member member) {
|
||||
useDevModeConfiguration();
|
||||
Class<?> memberClass = member.getDeclaringClass();
|
||||
if (isClassExcluded(memberClass)) {
|
||||
LOG.warn("Declaring class of member type [{}] is excluded!", memberClass);
|
||||
@@ -286,7 +337,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 +358,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 +388,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));
|
||||
}
|
||||
@@ -438,16 +481,21 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
this.enforceAllowlistEnabled = BooleanUtils.toBoolean(enforceAllowlistEnabled);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_CLASSES, required = false)
|
||||
@Inject(value = STRUTS_ALLOWLIST_CLASSES, required = false)
|
||||
public void useAllowlistClasses(String commaDelimitedClasses) {
|
||||
this.allowlistClasses = toClassObjectsSet(commaDelimitedClasses);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
|
||||
@Inject(value = STRUTS_ALLOWLIST_PACKAGE_NAMES, required = false)
|
||||
public void useAllowlistPackageNames(String commaDelimitedPackageNames) {
|
||||
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);
|
||||
@@ -457,4 +505,41 @@ public class SecurityMemberAccess implements MemberAccess {
|
||||
public void useDisallowDefaultPackageAccess(String disallowDefaultPackageAccess) {
|
||||
this.disallowDefaultPackageAccess = BooleanUtils.toBoolean(disallowDefaultPackageAccess);
|
||||
}
|
||||
|
||||
@Inject(StrutsConstants.STRUTS_DEVMODE)
|
||||
protected void useDevMode(String devMode) {
|
||||
this.isDevMode = BooleanUtils.toBoolean(devMode);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, required = false)
|
||||
public void useDevModeExcludedClasses(String commaDelimitedClasses) {
|
||||
this.devModeExcludedClasses = toNewClassesSet(devModeExcludedClasses, commaDelimitedClasses);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, required = false)
|
||||
public void useDevModeExcludedPackageNamePatterns(String commaDelimitedPackagePatterns) {
|
||||
this.devModeExcludedPackageNamePatterns = toNewPatternsSet(devModeExcludedPackageNamePatterns, commaDelimitedPackagePatterns);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, required = false)
|
||||
public void useDevModeExcludedPackageNames(String commaDelimitedPackageNames) {
|
||||
this.devModeExcludedPackageNames = toNewPackageNamesSet(devModeExcludedPackageNames, commaDelimitedPackageNames);
|
||||
}
|
||||
|
||||
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
|
||||
public void useDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
|
||||
this.devModeExcludedPackageExemptClasses = toClassesSet(commaDelimitedClasses);
|
||||
}
|
||||
|
||||
private void useDevModeConfiguration() {
|
||||
if (!isDevMode || isDevModeInit) {
|
||||
return;
|
||||
}
|
||||
isDevModeInit = true;
|
||||
LOG.warn("Working in devMode, using devMode excluded classes and packages!");
|
||||
excludedClasses = devModeExcludedClasses;
|
||||
excludedPackageNamePatterns = devModeExcludedPackageNamePatterns;
|
||||
excludedPackageNames = devModeExcludedPackageNames;
|
||||
excludedPackageExemptClasses = devModeExcludedPackageExemptClasses;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 com.opensymphony.xwork2.util;
|
||||
|
||||
import com.opensymphony.xwork2.TextProvider;
|
||||
import com.opensymphony.xwork2.interceptor.ValidationAware;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public final class DebugUtils {
|
||||
|
||||
public static void notifyDeveloperOfError(Logger log, Object action, String message) {
|
||||
if (action instanceof TextProvider) {
|
||||
TextProvider tp = (TextProvider) action;
|
||||
message = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{message});
|
||||
}
|
||||
log.error(message);
|
||||
if (action instanceof ValidationAware) {
|
||||
ValidationAware validationAware = (ValidationAware) action;
|
||||
validationAware.addActionError(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,13 +18,23 @@
|
||||
*/
|
||||
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.Hibernate;
|
||||
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;
|
||||
|
||||
import static java.lang.reflect.Modifier.isPublic;
|
||||
|
||||
/**
|
||||
* <code>ProxyUtil</code>
|
||||
@@ -38,11 +48,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 +87,7 @@ public class ProxyUtil {
|
||||
return flag;
|
||||
}
|
||||
|
||||
boolean isProxy = isSpringAopProxy(object);
|
||||
boolean isProxy = isSpringAopProxy(object) || isHibernateProxy(object);
|
||||
|
||||
isProxyCache.put(clazz, isProxy);
|
||||
return isProxy;
|
||||
@@ -87,7 +99,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 +108,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 —
|
||||
@@ -218,4 +258,34 @@ public class ProxyUtil {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the target instance of the given object if it is a Hibernate proxy object, otherwise the given object
|
||||
*/
|
||||
public static Object getHibernateProxyTarget(Object object) {
|
||||
try {
|
||||
return Hibernate.unproxy(object);
|
||||
} catch (NoClassDefFoundError ignored) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return matching member on target object if one exists, otherwise the same member
|
||||
*/
|
||||
public static Member resolveTargetMember(Member proxyMember, Object target) {
|
||||
int mod = proxyMember.getModifiers();
|
||||
if (proxyMember instanceof Method) {
|
||||
if (isPublic(mod)) {
|
||||
return MethodUtils.getMatchingAccessibleMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
|
||||
} else {
|
||||
return MethodUtils.getMatchingMethod(target.getClass(), proxyMember.getName(), ((Method) proxyMember).getParameterTypes());
|
||||
}
|
||||
} else if (proxyMember instanceof Field) {
|
||||
return FieldUtils.getField(target.getClass(), proxyMember.getName(), isPublic(mod));
|
||||
} else if (proxyMember instanceof Constructor && isPublic(mod)) {
|
||||
return ConstructorUtils.getMatchingAccessibleConstructor(target.getClass(), ((Constructor<?>) proxyMember).getParameterTypes());
|
||||
}
|
||||
return proxyMember;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public interface ActionValidatorManager {
|
||||
* @param method the name of the method being invoked on the action - can be <tt>null</tt>.
|
||||
* @return a list of all validators for the given class and context.
|
||||
*/
|
||||
List<Validator> getValidators(Class clazz, String context, String method);
|
||||
List<Validator> getValidators(Class<?> clazz, String context, String method);
|
||||
|
||||
/**
|
||||
* Returns a list of validators for the given class and context. This is the primary
|
||||
@@ -46,7 +46,7 @@ public interface ActionValidatorManager {
|
||||
* @param context the context of the action class - can be <tt>null</tt>.
|
||||
* @return a list of all validators for the given class and context.
|
||||
*/
|
||||
List<Validator> getValidators(Class clazz, String context);
|
||||
List<Validator> getValidators(Class<?> clazz, String context);
|
||||
|
||||
/**
|
||||
* Validates the given object using action and its context.
|
||||
|
||||
+9
-9
@@ -125,7 +125,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
* @param context context
|
||||
* @return a validator key which is the class name plus context.
|
||||
*/
|
||||
protected String buildValidatorKey(Class clazz, String context) {
|
||||
protected String buildValidatorKey(Class<?> clazz, String context) {
|
||||
return clazz.getName() + "/" + context;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
|
||||
public synchronized List<Validator> getValidators(Class<?> clazz, String context, String method) {
|
||||
String validatorKey = buildValidatorKey(clazz, context);
|
||||
|
||||
if (!validatorCache.containsKey(validatorKey)) {
|
||||
@@ -158,7 +158,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<Validator> getValidators(Class clazz, String context) {
|
||||
public synchronized List<Validator> getValidators(Class<?> clazz, String context) {
|
||||
return getValidators(clazz, context, null);
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
* @param checked the set of previously checked class-contexts, null if none have been checked
|
||||
* @return a list of validator configs for the given class and context.
|
||||
*/
|
||||
protected List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
|
||||
protected List<ValidatorConfig> buildValidatorConfigs(Class<?> clazz, String context, boolean checkFile, Set<String> checked) {
|
||||
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
|
||||
|
||||
if (checked == null) {
|
||||
@@ -287,7 +287,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
}
|
||||
|
||||
if (clazz.isInterface()) {
|
||||
for (Class anInterface : clazz.getInterfaces()) {
|
||||
for (Class<?> anInterface : clazz.getInterfaces()) {
|
||||
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
|
||||
}
|
||||
} else {
|
||||
@@ -297,7 +297,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
}
|
||||
|
||||
// look for validators for implemented interfaces
|
||||
for (Class anInterface1 : clazz.getInterfaces()) {
|
||||
for (Class<?> anInterface1 : clazz.getInterfaces()) {
|
||||
if (checked.contains(anInterface1.getName())) {
|
||||
continue;
|
||||
}
|
||||
@@ -317,17 +317,17 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
|
||||
return validatorConfigs;
|
||||
}
|
||||
|
||||
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
|
||||
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class<?> aClass, String context, boolean checkFile) {
|
||||
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
|
||||
return loadFile(fileName, aClass, checkFile);
|
||||
}
|
||||
|
||||
protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
|
||||
protected List<ValidatorConfig> buildClassValidatorConfigs(Class<?> aClass, boolean checkFile) {
|
||||
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
|
||||
return loadFile(fileName, aClass, checkFile);
|
||||
}
|
||||
|
||||
protected List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
|
||||
protected List<ValidatorConfig> loadFile(String fileName, Class<?> clazz, boolean checkFile) {
|
||||
List<ValidatorConfig> retList = Collections.emptyList();
|
||||
|
||||
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
|
||||
|
||||
+11
-1
@@ -122,10 +122,15 @@ public class DelegatingValidatorContext implements ValidatorContext {
|
||||
return localeProvider.isValidLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale toLocale(String localeStr) {
|
||||
return localeProvider.toLocale(localeStr);
|
||||
}
|
||||
|
||||
public boolean hasKey(String key) {
|
||||
return textProvider.hasKey(key);
|
||||
}
|
||||
|
||||
|
||||
public String getText(String aTextName) {
|
||||
return textProvider.getText(aTextName);
|
||||
}
|
||||
@@ -280,6 +285,11 @@ public class DelegatingValidatorContext implements ValidatorContext {
|
||||
public boolean isValidLocale(Locale locale) {
|
||||
return getLocaleProvider().isValidLocale(locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale toLocale(String localeStr) {
|
||||
return getLocaleProvider().toLocale(localeStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -230,6 +230,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";
|
||||
@@ -480,6 +482,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";
|
||||
|
||||
|
||||
@@ -103,9 +103,9 @@ public class ServletUrlRenderer implements UrlRenderer {
|
||||
}
|
||||
result = urlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
|
||||
}
|
||||
String anchor = urlComponent.getAnchor();
|
||||
if (StringUtils.isNotEmpty(anchor)) {
|
||||
result += '#' + urlComponent.findString(anchor);
|
||||
if (StringUtils.isNotEmpty(urlComponent.getAnchor())) {
|
||||
String anchor = urlComponent.findString(urlComponent.getAnchor());
|
||||
result += '#' + anchor;
|
||||
}
|
||||
|
||||
if (urlComponent.isPutInContext()) {
|
||||
@@ -292,7 +292,7 @@ public class ServletUrlRenderer implements UrlRenderer {
|
||||
private void includeGetParameters(UrlProvider urlComponent) {
|
||||
String query = extractQueryString(urlComponent);
|
||||
QueryStringParser.Result result = queryStringParser.parse(query);
|
||||
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
|
||||
result = mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
|
||||
if (!result.getQueryFragment().isEmpty()) {
|
||||
urlComponent.setAnchor(result.getQueryFragment());
|
||||
}
|
||||
@@ -331,10 +331,11 @@ public class ServletUrlRenderer implements UrlRenderer {
|
||||
* @param value the value attribute (URL to be generated by this component)
|
||||
* @param parameters component parameters
|
||||
* @param contextParameters request parameters
|
||||
* @return {@link QueryStringParser.Result} of value's ?query-string or empty()
|
||||
*/
|
||||
protected void mergeRequestParameters(String value, Map<String, Object> parameters, Map<String, ?> contextParameters) {
|
||||
|
||||
protected QueryStringParser.Result mergeRequestParameters(String value, Map<String, Object> parameters, Map<String, ?> contextParameters) {
|
||||
Map<String, Object> mergedParams = new LinkedHashMap<>(contextParameters);
|
||||
QueryStringParser.Result result = queryStringParser.empty();
|
||||
|
||||
// Merge contextParameters (from current request) with parameters specified in value attribute
|
||||
// eg. value="someAction.action?id=someId&venue=someVenue"
|
||||
@@ -343,7 +344,8 @@ public class ServletUrlRenderer implements UrlRenderer {
|
||||
if (StringUtils.contains(value, "?")) {
|
||||
String queryString = value.substring(value.indexOf('?') + 1);
|
||||
|
||||
mergedParams = new LinkedHashMap<>(queryStringParser.parse(queryString).getQueryParams());
|
||||
result = queryStringParser.parse(queryString);
|
||||
mergedParams = new LinkedHashMap<>(result.getQueryParams());
|
||||
for (Map.Entry<String, ?> entry : contextParameters.entrySet()) {
|
||||
if (!mergedParams.containsKey(entry.getKey())) {
|
||||
mergedParams.put(entry.getKey(), entry.getValue());
|
||||
@@ -362,6 +364,8 @@ public class ServletUrlRenderer implements UrlRenderer {
|
||||
parameters.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,17 +104,17 @@ public class Set extends ContextBean {
|
||||
body="";
|
||||
|
||||
if (DispatcherConstants.APPLICATION.equalsIgnoreCase(scope)) {
|
||||
stack.setValue("#application['" + getVar() + "']", o);
|
||||
stack.setValue(String.format("#application[\"%s\"]", getVar()), o);
|
||||
} else if (DispatcherConstants.SESSION.equalsIgnoreCase(scope)) {
|
||||
stack.setValue("#session['" + getVar() + "']", o);
|
||||
stack.setValue(String.format("#session[\"%s\"]", getVar()), o);
|
||||
} else if (DispatcherConstants.REQUEST.equalsIgnoreCase(scope)) {
|
||||
stack.setValue("#request['" + getVar() + "']", o);
|
||||
stack.setValue(String.format("#request[\"%s\"]", getVar()), o);
|
||||
} else if (DispatcherConstants.PAGE.equalsIgnoreCase(scope)) {
|
||||
stack.setValue("#attr['" + getVar() + "']", o, false);
|
||||
stack.setValue(String.format("#attr[\"%s\"]", getVar()), o, false);
|
||||
} else {
|
||||
// Default scope is action. Note: The action scope handling also adds the var to the page scope.
|
||||
stack.getContext().put(getVar(), o);
|
||||
stack.setValue("#attr['" + getVar() + "']", o, false);
|
||||
putInContext(o);
|
||||
stack.setValue(String.format("#attr[\"%s\"]", getVar()), o, false);
|
||||
}
|
||||
|
||||
return super.end(writer, body);
|
||||
|
||||
@@ -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 javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -88,6 +89,10 @@ import java.util.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);
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.opensymphony.xwork2.LocaleProviderFactory;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
|
||||
import com.opensymphony.xwork2.util.TextParseUtil;
|
||||
import org.apache.commons.lang3.LocaleUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
@@ -85,7 +84,7 @@ public class I18nInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
|
||||
public void setLocaleStorage(String storageName) {
|
||||
if (storageName == null || "".equals(storageName)) {
|
||||
if (storageName == null || storageName.isEmpty()) {
|
||||
this.storage = Storage.ACCEPT_LANGUAGE;
|
||||
} else {
|
||||
try {
|
||||
@@ -169,27 +168,21 @@ public class I18nInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Locale object from the request param, which might
|
||||
* be already a Local or a String
|
||||
* Creates a Locale object from the request param
|
||||
*
|
||||
* @param requestedLocale the parameter from the request
|
||||
* @return the Locale
|
||||
* @return instance of {@link Locale} or null
|
||||
*/
|
||||
protected Locale getLocaleFromParam(Object requestedLocale) {
|
||||
protected Locale getLocaleFromParam(String requestedLocale) {
|
||||
LocaleProvider localeProvider = localeProviderFactory.createLocaleProvider();
|
||||
|
||||
Locale locale = null;
|
||||
if (requestedLocale != null) {
|
||||
if (requestedLocale instanceof Locale) {
|
||||
locale = (Locale) requestedLocale;
|
||||
} else {
|
||||
String localeStr = requestedLocale.toString();
|
||||
if (localeProvider.isValidLocaleString(localeStr)) {
|
||||
locale = LocaleUtils.toLocale(localeStr);
|
||||
} else {
|
||||
locale = localeProvider.getLocale();
|
||||
}
|
||||
locale = localeProvider.toLocale(requestedLocale);
|
||||
if (locale == null) {
|
||||
locale = localeProvider.getLocale();
|
||||
}
|
||||
|
||||
if (locale != null) {
|
||||
LOG.debug("Found locale: {}", locale);
|
||||
}
|
||||
@@ -285,7 +278,7 @@ public class I18nInterceptor extends AbstractInterceptor {
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public Locale find() {
|
||||
if (supportedLocale.size() > 0) {
|
||||
if (!supportedLocale.isEmpty()) {
|
||||
Enumeration locales = actionInvocation.getInvocationContext().getServletRequest().getLocales();
|
||||
while (locales.hasMoreElements()) {
|
||||
Locale locale = (Locale) locales.nextElement();
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
package org.apache.struts2.interceptor.csp;
|
||||
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
|
||||
import com.opensymphony.xwork2.util.ClassLoaderUtil;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.action.CspSettingsAware;
|
||||
@@ -46,6 +48,9 @@ public final class CspInterceptor extends AbstractInterceptor {
|
||||
private boolean prependServletContext = true;
|
||||
private boolean enforcingMode;
|
||||
private String reportUri;
|
||||
private String reportTo;
|
||||
|
||||
private String cspSettingsClassName = DefaultCspSettings.class.getName();
|
||||
|
||||
@Override
|
||||
public String intercept(ActionInvocation invocation) throws Exception {
|
||||
@@ -54,12 +59,30 @@ public final class CspInterceptor extends AbstractInterceptor {
|
||||
LOG.trace("Using CspSettings provided by the action: {}", action);
|
||||
applySettings(invocation, ((CspSettingsAware) action).getCspSettings());
|
||||
} else {
|
||||
LOG.trace("Using DefaultCspSettings with action: {}", action);
|
||||
applySettings(invocation, new DefaultCspSettings());
|
||||
LOG.trace("Using {} with action: {}", cspSettingsClassName, action);
|
||||
CspSettings cspSettings = createCspSettings(invocation);
|
||||
applySettings(invocation, cspSettings);
|
||||
}
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
private CspSettings createCspSettings(ActionInvocation invocation) throws ClassNotFoundException {
|
||||
Class<?> cspSettingsClass;
|
||||
|
||||
try {
|
||||
cspSettingsClass = ClassLoaderUtil.loadClass(cspSettingsClassName, getClass());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new ConfigurationException(String.format("The class %s doesn't exist!", cspSettingsClassName));
|
||||
}
|
||||
|
||||
if (!CspSettings.class.isAssignableFrom(cspSettingsClass)) {
|
||||
throw new ConfigurationException(String.format("The class %s doesn't implement %s!",
|
||||
cspSettingsClassName, CspSettings.class.getName()));
|
||||
}
|
||||
|
||||
return (CspSettings) invocation.getInvocationContext().getContainer().inject(cspSettingsClass);
|
||||
}
|
||||
|
||||
private void applySettings(ActionInvocation invocation, CspSettings cspSettings) {
|
||||
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
|
||||
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
|
||||
@@ -76,6 +99,12 @@ public final class CspInterceptor extends AbstractInterceptor {
|
||||
}
|
||||
|
||||
cspSettings.setReportUri(finalReportUri);
|
||||
|
||||
// apply reportTo if set
|
||||
if (reportTo != null) {
|
||||
LOG.trace("Applying: {} to reportTo", reportTo);
|
||||
cspSettings.setReportTo(reportTo);
|
||||
}
|
||||
}
|
||||
|
||||
invocation.addPreResultListener((actionInvocation, resultCode) -> {
|
||||
@@ -97,6 +126,17 @@ public final class CspInterceptor extends AbstractInterceptor {
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the report group where csp violation reports will be sent. This will
|
||||
* only be used if the reportUri is set.
|
||||
*
|
||||
* @param reportTo the report group where csp violation reports will be sent
|
||||
* @since Struts 6.5.0
|
||||
*/
|
||||
public void setReportTo(String reportTo) {
|
||||
this.reportTo = reportTo;
|
||||
}
|
||||
|
||||
private Optional<URI> buildUri(String reportUri) {
|
||||
try {
|
||||
return Optional.of(URI.create(reportUri));
|
||||
@@ -124,4 +164,13 @@ public final class CspInterceptor extends AbstractInterceptor {
|
||||
this.prependServletContext = prependServletContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the class name of the default {@link CspSettings} implementation to use when the action does not
|
||||
* set its own values. If not set, the default is {@link DefaultCspSettings}.
|
||||
*
|
||||
* @since Struts 6.5.0
|
||||
*/
|
||||
public void setCspSettingsClassName(String cspSettingsClassName) {
|
||||
this.cspSettingsClassName = cspSettingsClassName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public interface CspSettings {
|
||||
String SCRIPT_SRC = "script-src";
|
||||
String BASE_URI = "base-uri";
|
||||
String REPORT_URI = "report-uri";
|
||||
String REPORT_TO = "report-to";
|
||||
String NONE = "none";
|
||||
String STRICT_DYNAMIC = "strict-dynamic";
|
||||
String HTTP = "http:";
|
||||
@@ -56,6 +57,13 @@ public interface CspSettings {
|
||||
*/
|
||||
void setReportUri(String uri);
|
||||
|
||||
/**
|
||||
* Sets the report group where csp violation reports will be sent
|
||||
*
|
||||
* @since Struts 6.5.0
|
||||
*/
|
||||
void setReportTo(String group);
|
||||
|
||||
/**
|
||||
* Sets CSP headers in enforcing mode when true, and report-only when false
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.apache.struts2.interceptor.csp;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.action.CspSettingsAware;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -31,7 +32,11 @@ import static java.lang.String.format;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link CspSettings}.
|
||||
* The default policy implements strict CSP with a nonce based approach and follows the guide: <a href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html/</a>
|
||||
* The default policy implements strict CSP with a nonce based approach and follows the guide:
|
||||
* <a href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html/</a>
|
||||
* You may extend or replace this class if you wish to customize the default policy further, and use your class
|
||||
* by setting the {@link CspInterceptor} defaultCspSettingsClassName parameter. Actions that
|
||||
* implement the {@link CspSettingsAware} interface will ignore the defaultCspSettingsClassName parameter.
|
||||
*
|
||||
* @see CspSettings
|
||||
* @see CspInterceptor
|
||||
@@ -42,20 +47,22 @@ public class DefaultCspSettings implements CspSettings {
|
||||
|
||||
private final SecureRandom sRand = new SecureRandom();
|
||||
|
||||
private String reportUri;
|
||||
protected String reportUri;
|
||||
protected String reportTo;
|
||||
// default to reporting mode
|
||||
private String cspHeader = CSP_REPORT_HEADER;
|
||||
protected String cspHeader = CSP_REPORT_HEADER;
|
||||
|
||||
@Override
|
||||
public void addCspHeaders(HttpServletResponse response) {
|
||||
throw new UnsupportedOperationException("Unsupported implementation, use #addCspHeaders(HttpServletRequest request, HttpServletResponse response)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCspHeaders(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (isSessionActive(request)) {
|
||||
LOG.trace("Session is active, applying CSP settings");
|
||||
associateNonceWithSession(request);
|
||||
response.setHeader(cspHeader, cratePolicyFormat(request));
|
||||
response.setHeader(cspHeader, createPolicyFormat(request));
|
||||
} else {
|
||||
LOG.trace("Session is not active, ignoring CSP settings");
|
||||
}
|
||||
@@ -70,7 +77,7 @@ public class DefaultCspSettings implements CspSettings {
|
||||
request.getSession().setAttribute("nonce", nonceValue);
|
||||
}
|
||||
|
||||
private String cratePolicyFormat(HttpServletRequest request) {
|
||||
protected String createPolicyFormat(HttpServletRequest request) {
|
||||
StringBuilder policyFormatBuilder = new StringBuilder()
|
||||
.append(OBJECT_SRC)
|
||||
.append(format(" '%s'; ", NONE))
|
||||
@@ -84,13 +91,18 @@ public class DefaultCspSettings implements CspSettings {
|
||||
if (reportUri != null) {
|
||||
policyFormatBuilder
|
||||
.append(REPORT_URI)
|
||||
.append(format(" %s", reportUri));
|
||||
.append(format(" %s; ", reportUri));
|
||||
if(reportTo != null) {
|
||||
policyFormatBuilder
|
||||
.append(REPORT_TO)
|
||||
.append(format(" %s; ", reportTo));
|
||||
}
|
||||
}
|
||||
|
||||
return format(policyFormatBuilder.toString(), getNonceString(request));
|
||||
}
|
||||
|
||||
private String getNonceString(HttpServletRequest request) {
|
||||
protected String getNonceString(HttpServletRequest request) {
|
||||
Object nonce = request.getSession().getAttribute("nonce");
|
||||
return Objects.toString(nonce);
|
||||
}
|
||||
@@ -101,20 +113,28 @@ public class DefaultCspSettings implements CspSettings {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnforcingMode(boolean enforcingMode) {
|
||||
if (enforcingMode) {
|
||||
cspHeader = CSP_ENFORCE_HEADER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReportUri(String reportUri) {
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReportTo(String reportTo) {
|
||||
this.reportTo = reportTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultCspSettings{" +
|
||||
"reportUri='" + reportUri + '\'' +
|
||||
", reportTo='" + reportTo + '\'' +
|
||||
", cspHeader='" + cspHeader + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
+16
-4
@@ -20,6 +20,8 @@ package org.apache.struts2.interceptor.exec;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -30,6 +32,8 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
|
||||
|
||||
private static final long serialVersionUID = 3884464776311686443L;
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(StrutsBackgroundProcess.class);
|
||||
|
||||
private final String threadName;
|
||||
private final int threadPriority;
|
||||
|
||||
@@ -44,8 +48,8 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
|
||||
/**
|
||||
* Constructs a background process
|
||||
*
|
||||
* @param invocation The action invocation
|
||||
* @param threadName The name of background thread
|
||||
* @param invocation The action invocation
|
||||
* @param threadName The name of background thread
|
||||
* @param threadPriority The priority of background thread
|
||||
*/
|
||||
public StrutsBackgroundProcess(ActionInvocation invocation, String threadName, int threadPriority) {
|
||||
@@ -61,11 +65,19 @@ public class StrutsBackgroundProcess implements BackgroundProcess, Serializable
|
||||
try {
|
||||
beforeInvocation();
|
||||
result = invocation.invokeActionOnly();
|
||||
afterInvocation();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Exception during invokeActionOnly() execution", e);
|
||||
exception = e;
|
||||
} finally {
|
||||
done = true;
|
||||
try {
|
||||
afterInvocation();
|
||||
} catch (Exception ex) {
|
||||
if (exception == null) {
|
||||
exception = ex;
|
||||
}
|
||||
LOG.warn("Exception during afterInvocation() execution", ex);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
});
|
||||
processThread.setName(threadName);
|
||||
|
||||
+29
-22
@@ -20,10 +20,8 @@ package org.apache.struts2.interceptor.parameter;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.TextProvider;
|
||||
import com.opensymphony.xwork2.inject.Inject;
|
||||
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
|
||||
import com.opensymphony.xwork2.interceptor.ValidationAware;
|
||||
import com.opensymphony.xwork2.security.AcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker;
|
||||
import com.opensymphony.xwork2.security.ExcludedPatternsChecker;
|
||||
@@ -56,7 +54,6 @@ import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -67,6 +64,8 @@ import java.util.regex.Pattern;
|
||||
|
||||
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS;
|
||||
import static com.opensymphony.xwork2.security.DefaultAcceptedPatternsChecker.NESTING_CHARS_STR;
|
||||
import static com.opensymphony.xwork2.util.DebugUtils.notifyDeveloperOfError;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Collections.unmodifiableSet;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.apache.commons.lang3.StringUtils.indexOfAny;
|
||||
@@ -317,19 +316,8 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
}
|
||||
|
||||
protected void notifyDeveloperParameterException(Object action, String property, String message) {
|
||||
String logMsg = "Unexpected Exception caught setting '" + property + "' on '" + action.getClass() + ": " + message;
|
||||
if (action instanceof TextProvider) {
|
||||
TextProvider tp = (TextProvider) action;
|
||||
logMsg = tp.getText("devmode.notification", "Developer Notification:\n{0}", new String[]{logMsg});
|
||||
}
|
||||
LOG.error(logMsg);
|
||||
|
||||
if (action instanceof ValidationAware) {
|
||||
ValidationAware validationAware = (ValidationAware) action;
|
||||
Collection<String> messages = validationAware.getActionMessages();
|
||||
messages.add(message);
|
||||
validationAware.setActionMessages(messages);
|
||||
}
|
||||
String logMsg = format("Unexpected Exception caught setting '%s' on '%s: %s", property, action.getClass(), message);
|
||||
notifyDeveloperOfError(LOG, action, logMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,23 +376,37 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
return hasValidAnnotatedField(action, rootProperty, paramDepth);
|
||||
}
|
||||
|
||||
if (hasValidAnnotatedPropertyDescriptor(propDescOpt.get(), paramDepth)) {
|
||||
if (hasValidAnnotatedPropertyDescriptor(action, propDescOpt.get(), paramDepth)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hasValidAnnotatedField(action, rootProperty, paramDepth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 6.5.0, use {@link #hasValidAnnotatedPropertyDescriptor(Object, PropertyDescriptor, long)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
protected boolean hasValidAnnotatedPropertyDescriptor(PropertyDescriptor propDesc, long paramDepth) {
|
||||
return hasValidAnnotatedPropertyDescriptor(null, propDesc, paramDepth);
|
||||
}
|
||||
|
||||
protected boolean hasValidAnnotatedPropertyDescriptor(Object action, PropertyDescriptor propDesc, long paramDepth) {
|
||||
Method relevantMethod = paramDepth == 0 ? propDesc.getWriteMethod() : propDesc.getReadMethod();
|
||||
if (relevantMethod == null) {
|
||||
return false;
|
||||
}
|
||||
if (getPermittedInjectionDepth(relevantMethod) < paramDepth) {
|
||||
LOG.debug(
|
||||
"Parameter injection for method [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
String logMessage = format(
|
||||
"Parameter injection for method [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
relevantMethod.getName(),
|
||||
relevantMethod.getDeclaringClass().getName());
|
||||
if (devMode) {
|
||||
notifyDeveloperOfError(LOG, action, logMessage);
|
||||
} else {
|
||||
LOG.debug(logMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (paramDepth >= 1) {
|
||||
@@ -455,10 +457,15 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
return false;
|
||||
}
|
||||
if (getPermittedInjectionDepth(field) < paramDepth) {
|
||||
LOG.debug(
|
||||
"Parameter injection for field [{}] on action [{}] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
String logMessage = format(
|
||||
"Parameter injection for field [%s] on action [%s] rejected. Ensure it is annotated with @StrutsParameter with an appropriate 'depth'.",
|
||||
fieldName,
|
||||
action.getClass().getName());
|
||||
if (devMode) {
|
||||
notifyDeveloperOfError(LOG, action, logMessage);
|
||||
} else {
|
||||
LOG.debug(logMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (paramDepth >= 1) {
|
||||
@@ -533,7 +540,7 @@ public class ParametersInterceptor extends MethodFilterInterceptor {
|
||||
return "NONE";
|
||||
}
|
||||
return parameters.entrySet().stream()
|
||||
.map(entry -> String.format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
|
||||
.map(entry -> format("%s => %s ", entry.getKey(), entry.getValue().getValue()))
|
||||
.collect(joining());
|
||||
}
|
||||
|
||||
|
||||
@@ -118,9 +118,6 @@ public class FreemarkerManager {
|
||||
public static final String INITPARAM_DEBUG = "Debug";
|
||||
|
||||
public static final String KEY_REQUEST = "Request";
|
||||
public static final String KEY_INCLUDE = "include_page";
|
||||
public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
|
||||
public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
|
||||
public static final String KEY_SESSION = "Session";
|
||||
public static final String KEY_APPLICATION = "Application";
|
||||
public static final String KEY_APPLICATION_PRIVATE = "__FreeMarkerServlet.Application__";
|
||||
@@ -138,10 +135,29 @@ public class FreemarkerManager {
|
||||
// for Struts
|
||||
public static final String KEY_REQUEST_PARAMETERS_STRUTS = "Parameters";
|
||||
|
||||
public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
|
||||
|
||||
public static final String EXPIRATION_DATE;
|
||||
|
||||
/**
|
||||
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String KEY_INCLUDE = "include_page";
|
||||
/**
|
||||
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String KEY_REQUEST_PRIVATE = "__FreeMarkerServlet.Request__";
|
||||
/**
|
||||
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String KEY_REQUEST_PARAMETERS = "RequestParameters";
|
||||
/**
|
||||
* @deprecated since Struts 6.5.0, do not use as it will be removed in Struts 7.0.0
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String KEY_HASHMODEL_PRIVATE = "__FreeMarkerManager.Request__";
|
||||
|
||||
/**
|
||||
* Adds individual settings.
|
||||
*
|
||||
|
||||
@@ -215,6 +215,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,
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 com.opensymphony.xwork2;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DefaultLocaleProviderTest {
|
||||
|
||||
private DefaultLocaleProvider provider;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
provider = new DefaultLocaleProvider();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ActionContext.of().bind();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ActionContext.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLocale() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.ITALY);
|
||||
|
||||
// when
|
||||
Locale actual = provider.getLocale();
|
||||
|
||||
// then
|
||||
assertEquals(Locale.ITALY, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getLocaleNull() {
|
||||
// given
|
||||
ActionContext backup = ActionContext.getContext();
|
||||
ActionContext.clear();
|
||||
|
||||
// when
|
||||
Locale actual = provider.getLocale();
|
||||
|
||||
// then
|
||||
assertNull(actual);
|
||||
ActionContext.bind(backup);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocale() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
Locale actual = provider.toLocale("it");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.ITALIAN, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocaleFull() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
Locale actual = provider.toLocale("it_IT");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.ITALY, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocaleTrimEndOfLine() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
Locale actual = provider.toLocale("it_IT\n");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.ITALY, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocaleTrimEmptySpace() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
Locale actual = provider.toLocale(" it_IT ");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.ITALY, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidLocaleNull() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
boolean actual = provider.isValidLocale(null);
|
||||
|
||||
// then
|
||||
assertFalse(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidLocale() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
boolean actual = provider.isValidLocale(Locale.ITALIAN);
|
||||
|
||||
// then
|
||||
assertTrue(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidLocaleString() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
boolean actual = provider.isValidLocaleString("it");
|
||||
|
||||
// then
|
||||
assertTrue(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isValidLocaleStringNot() {
|
||||
// given
|
||||
ActionContext.getContext().withLocale(Locale.GERMAN);
|
||||
|
||||
// when
|
||||
boolean actual = provider.isValidLocaleString("italy");
|
||||
|
||||
// then
|
||||
assertFalse(actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 com.opensymphony.xwork2;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class LocaleProviderTest {
|
||||
|
||||
@Test
|
||||
public void toLocale() {
|
||||
// given
|
||||
DummyLocale locale = new DummyLocale();
|
||||
|
||||
// when
|
||||
Locale actual = locale.toLocale("de");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.GERMAN, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocaleTrim() {
|
||||
// given
|
||||
DummyLocale locale = new DummyLocale();
|
||||
|
||||
// when
|
||||
Locale actual = locale.toLocale(" de_DE ");
|
||||
|
||||
// then
|
||||
assertEquals(Locale.GERMANY, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLocaleNull() {
|
||||
// given
|
||||
DummyLocale locale = new DummyLocale();
|
||||
|
||||
// when
|
||||
Locale actual = locale.toLocale("germany");
|
||||
|
||||
// then
|
||||
assertNull(actual);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DummyLocale implements LocaleProvider {
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidLocaleString(String localeStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidLocale(Locale locale) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
ognlUtil = container.getInstance(OgnlUtil.class);
|
||||
}
|
||||
|
||||
private void resetOgnlUtil(Map<String, ?> properties) {
|
||||
loadButSet(properties);
|
||||
ognlUtil = container.getInstance(OgnlUtil.class);
|
||||
}
|
||||
|
||||
public void testCanSetADependentObject() {
|
||||
String dogName = "fido";
|
||||
|
||||
@@ -1152,8 +1157,8 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
ognlUtil.setValue("class.classLoader.defaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
// Object.class is excluded by default
|
||||
ognlUtil.setValue("class.classLoader", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
expected = e;
|
||||
@@ -1166,9 +1171,11 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
public void testAllowCallingMethodsOnObjectClassInDevModeTrue() {
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Foo.class.getName());
|
||||
ognlUtil.setDevModeExcludedClasses("");
|
||||
ognlUtil.setDevMode(Boolean.TRUE.toString());
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, Foo.class.getName());
|
||||
properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, "");
|
||||
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
|
||||
resetOgnlUtil(properties);
|
||||
|
||||
Foo foo = new Foo();
|
||||
String result = (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class);
|
||||
@@ -1180,14 +1187,18 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
}
|
||||
|
||||
public void testExclusionListDevModeOnOff() throws Exception {
|
||||
ognlUtil.setDevModeExcludedClasses(Foo.class.getName());
|
||||
Foo foo = new Foo();
|
||||
|
||||
ognlUtil.setDevMode(Boolean.TRUE.toString());
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, Foo.class.getName());
|
||||
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.TRUE.toString());
|
||||
resetOgnlUtil(properties);
|
||||
|
||||
OgnlException e = assertThrows(OgnlException.class, () -> ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
|
||||
assertThat(e).hasMessageContaining("com.opensymphony.xwork2.util.Foo.toString");
|
||||
|
||||
ognlUtil.setDevMode(Boolean.FALSE.toString());
|
||||
properties.put(StrutsConstants.STRUTS_DEVMODE, Boolean.FALSE.toString());
|
||||
resetOgnlUtil(properties);
|
||||
assertEquals("Foo", (String) ognlUtil.getValue("toString", ognlUtil.createDefaultContext(foo), foo, String.class));
|
||||
}
|
||||
|
||||
@@ -1196,7 +1207,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
// Object.class is excluded by default
|
||||
ognlUtil.setValue("Class.ClassLoader.DefaultAssertionStatus", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
@@ -1212,7 +1223,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
// Object.class is excluded by default
|
||||
ognlUtil.setValue("class['classLoader']['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
@@ -1243,7 +1254,7 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Object.class.getName());
|
||||
// Object.class is excluded by default
|
||||
ognlUtil.setValue("class[\"classLoader\"]['defaultAssertionStatus']", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
@@ -1284,12 +1295,11 @@ public class OgnlUtilTest extends XWorkTestCase {
|
||||
assertEquals(expected.getMessage(), "Inappropriate OGNL expression: toString()");
|
||||
}
|
||||
|
||||
public void testAvoidCallingSomeClasses() {
|
||||
public void testStaticMethodBlocked() {
|
||||
Foo foo = new Foo();
|
||||
|
||||
Exception expected = null;
|
||||
try {
|
||||
ognlUtil.setExcludedClasses(Runtime.class.getName());
|
||||
ognlUtil.setValue("@java.lang.Runtime@getRuntime().exec('mate')", ognlUtil.createDefaultContext(foo), foo, true);
|
||||
fail();
|
||||
} catch (OgnlException e) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -26,12 +26,16 @@ import ognl.MemberAccess;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.apache.struts2.ognl.ProviderAllowlist;
|
||||
import org.apache.struts2.ognl.ThreadAllowlist;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -413,7 +417,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
|
||||
@@ -853,9 +857,11 @@ public class SecurityMemberAccessTest {
|
||||
assertTrue("package java.lang. is accessible!", actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the allowlist is enforced correctly for classes.
|
||||
*/
|
||||
@Test
|
||||
public void classInclusion() throws Exception {
|
||||
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
|
||||
TestBean2 bean = new TestBean2();
|
||||
@@ -868,6 +874,9 @@ public class SecurityMemberAccessTest {
|
||||
assertTrue(sma.checkAllowlist(bean, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the allowlist is enforced correctly for packages.
|
||||
*/
|
||||
@Test
|
||||
public void packageInclusion() throws Exception {
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
@@ -882,6 +891,9 @@ public class SecurityMemberAccessTest {
|
||||
assertTrue(sma.checkAllowlist(bean, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the allowlist doesn't allow inherited methods unless the declaring class is also allowlisted.
|
||||
*/
|
||||
@Test
|
||||
public void classInclusion_subclass() throws Exception {
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
@@ -893,6 +905,9 @@ public class SecurityMemberAccessTest {
|
||||
assertFalse(sma.checkAllowlist(bean, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the allowlist allows inherited methods when both the target and declaring class are allowlisted.
|
||||
*/
|
||||
@Test
|
||||
public void classInclusion_subclass_both() throws Exception {
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
@@ -904,6 +919,10 @@ public class SecurityMemberAccessTest {
|
||||
assertTrue(sma.checkAllowlist(bean, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the allowlist doesn't allow inherited methods unless the package of the declaring class is also
|
||||
* allowlisted.
|
||||
*/
|
||||
@Test
|
||||
public void packageInclusion_subclass() throws Exception {
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
@@ -915,6 +934,37 @@ public class SecurityMemberAccessTest {
|
||||
assertFalse(sma.checkAllowlist(bean, method));
|
||||
}
|
||||
|
||||
/**
|
||||
* When the allowlist is enabled and proxy object access is disallowed, Hibernate proxies should not be allowed.
|
||||
*/
|
||||
@Test
|
||||
public void classInclusion_hibernateProxy_disallowProxyObjectAccess() throws Exception {
|
||||
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
|
||||
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
|
||||
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
|
||||
sma.useAllowlistClasses(FooBar.class.getName());
|
||||
|
||||
assertFalse(sma.checkAllowlist(proxyObject, proxyMethod));
|
||||
}
|
||||
|
||||
/**
|
||||
* When the allowlist is enabled and proxy object access is allowed, Hibernate proxies should be allowlisted based
|
||||
* on their underlying target object. Class allowlisting should work as expected.
|
||||
*/
|
||||
@Test
|
||||
public void classInclusion_hibernateProxy_allowProxyObjectAccess() throws Exception {
|
||||
FooBarInterface proxyObject = mockHibernateProxy(new FooBar(), FooBarInterface.class);
|
||||
Method proxyMethod = proxyObject.getClass().getMethod("fooLogic");
|
||||
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
|
||||
sma.useAllowlistClasses(FooBar.class.getName());
|
||||
|
||||
assertTrue(sma.checkAllowlist(proxyObject, proxyMethod));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void packageInclusion_subclass_both() throws Exception {
|
||||
sma.useEnforceAllowlistEnabled(Boolean.TRUE.toString());
|
||||
@@ -931,6 +981,15 @@ public class SecurityMemberAccessTest {
|
||||
private static String formGetterName(String propertyName) {
|
||||
return "get" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T mockHibernateProxy(T originalObject, Class<T> proxyInterface) {
|
||||
return (T) Proxy.newProxyInstance(
|
||||
proxyInterface.getClassLoader(),
|
||||
new Class<?>[]{proxyInterface, HibernateProxy.class},
|
||||
new DummyHibernateProxyHandler(originalObject)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FooBar implements FooBarInterface {
|
||||
@@ -1042,10 +1101,28 @@ class StaticTester {
|
||||
}
|
||||
|
||||
protected static Field getFieldByName(String fieldName) throws NoSuchFieldException {
|
||||
if (fieldName != null && fieldName.length() > 0) {
|
||||
if (fieldName != null && !fieldName.isEmpty()) {
|
||||
return StaticTester.class.getDeclaredField(fieldName);
|
||||
} else {
|
||||
throw new NoSuchFieldException("field: " + fieldName + " does not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DummyHibernateProxyHandler implements InvocationHandler {
|
||||
private final Object instance;
|
||||
|
||||
public DummyHibernateProxyHandler(Object instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if (HibernateProxy.class.getMethod("getHibernateLazyInitializer").equals(method)) {
|
||||
LazyInitializer initializer = mock(LazyInitializer.class);
|
||||
when(initializer.getImplementation()).thenReturn(instance);
|
||||
return initializer;
|
||||
}
|
||||
return method.invoke(instance, args);
|
||||
}
|
||||
}
|
||||
|
||||
+175
-181
@@ -18,161 +18,167 @@
|
||||
*/
|
||||
package com.opensymphony.xwork2.validator;
|
||||
|
||||
import com.mockobjects.dynamic.C;
|
||||
import com.mockobjects.dynamic.Mock;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.FileManagerFactory;
|
||||
import com.opensymphony.xwork2.SimpleAction;
|
||||
import com.opensymphony.xwork2.StubValueStack;
|
||||
import com.opensymphony.xwork2.TestBean;
|
||||
import com.opensymphony.xwork2.ValidationOrderAction;
|
||||
import com.opensymphony.xwork2.XWorkTestCase;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
import com.opensymphony.xwork2.interceptor.ValidationAware;
|
||||
import com.opensymphony.xwork2.test.DataAware2;
|
||||
import com.opensymphony.xwork2.test.SimpleAction2;
|
||||
import com.opensymphony.xwork2.test.SimpleAction3;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
import com.opensymphony.xwork2.util.fs.DefaultFileManager;
|
||||
import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory;
|
||||
import com.opensymphony.xwork2.test.User;
|
||||
import com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.ExpressionValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.RequiredFieldValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.RequiredStringValidator;
|
||||
import com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator;
|
||||
import org.apache.struts2.StrutsException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* DefaultActionValidatorManagerTest
|
||||
*
|
||||
* @author Jason Carreira
|
||||
* @author tm_jee
|
||||
* @version $Date$ $Id$
|
||||
*/
|
||||
public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
|
||||
protected final String alias = "validationAlias";
|
||||
|
||||
DefaultActionValidatorManager actionValidatorManager;
|
||||
Mock mockValidatorFileParser;
|
||||
Mock mockValidatorFactory;
|
||||
ValueStack stubValueStack;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
actionValidatorManager = new DefaultActionValidatorManager();
|
||||
super.setUp();
|
||||
mockValidatorFileParser = new Mock(ValidatorFileParser.class);
|
||||
actionValidatorManager.setValidatorFileParser((ValidatorFileParser)mockValidatorFileParser.proxy());
|
||||
|
||||
mockValidatorFactory = new Mock(ValidatorFactory.class);
|
||||
actionValidatorManager.setValidatorFactory((ValidatorFactory)mockValidatorFactory.proxy());
|
||||
|
||||
stubValueStack = new StubValueStack();
|
||||
ActionContext.of()
|
||||
.withValueStack(stubValueStack)
|
||||
.bind();
|
||||
|
||||
DefaultFileManagerFactory factory = new DefaultFileManagerFactory();
|
||||
factory.setContainer(container);
|
||||
factory.setFileManager(new DefaultFileManager());
|
||||
actionValidatorManager.setFileManagerFactory(factory);
|
||||
actionValidatorManager = container.inject(DefaultActionValidatorManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
actionValidatorManager = null;
|
||||
super.tearDown();
|
||||
mockValidatorFactory = null;
|
||||
mockValidatorFileParser = null;
|
||||
actionValidatorManager = null;
|
||||
}
|
||||
|
||||
|
||||
public void testBuildValidatorKey() {
|
||||
String validatorKey = actionValidatorManager.buildValidatorKey(SimpleAction.class, alias);
|
||||
assertEquals(SimpleAction.class.getName() + "/" + alias, validatorKey);
|
||||
}
|
||||
|
||||
public void testBuildsValidatorsForAlias() {
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
actionValidatorManager.getValidators(SimpleAction.class, alias);
|
||||
mockValidatorFileParser.verify();
|
||||
List<Validator> validators = actionValidatorManager.getValidators(SimpleAction.class, alias);
|
||||
|
||||
assertThat(validators).hasSize(11).map(Validator::getClass).containsExactly(
|
||||
ExpressionValidator.class,
|
||||
RequiredFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
DoubleRangeFieldValidator.class,
|
||||
DateRangeFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
LongRangeFieldValidator.class,
|
||||
ShortRangeFieldValidator.class,
|
||||
RequiredFieldValidator.class,
|
||||
IntRangeFieldValidator.class
|
||||
);
|
||||
assertThat(validators).hasSize(11).map(Validator::getDefaultMessage).containsExactly(
|
||||
"Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}.",
|
||||
"You must enter a value for bar.",
|
||||
"bar must be between ${min} and ${max}, current value is ${bar}.",
|
||||
"percentage must be between ${minExclusive} and ${maxExclusive}, current value is ${percentage}.",
|
||||
"The date must be between 12-22-2002 and 12-25-2002.",
|
||||
"Could not find foo.range!",
|
||||
"Could not find baz.range!",
|
||||
"Could not find foo.range!",
|
||||
"Could not find foo.range!",
|
||||
"You must enter a value for baz.",
|
||||
"baz out of range."
|
||||
);
|
||||
}
|
||||
|
||||
public void testBuildsValidatorsForAliasError() {
|
||||
boolean pass = false;
|
||||
try {
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndThrow("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/TestBean-badtest-validation.xml")),
|
||||
new ConfigurationException());
|
||||
List validatorList = actionValidatorManager.getValidators(TestBean.class, "badtest");
|
||||
} catch (StrutsException ex) {
|
||||
pass = true;
|
||||
}
|
||||
mockValidatorFileParser.verify();
|
||||
assertTrue("Didn't throw exception on load failure", pass);
|
||||
assertThatThrownBy(() -> actionValidatorManager.getValidators(TestBean.class, "badtest"))
|
||||
.isInstanceOf(StrutsException.class)
|
||||
.hasCause(new SAXParseException("Attribute \"foo\" must be declared for element type \"field-validator\".", null));
|
||||
}
|
||||
|
||||
|
||||
public void testGetValidatorsForInterface() {
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware2-validation.xml")),
|
||||
new ArrayList());
|
||||
actionValidatorManager.getValidators(DataAware2.class, alias);
|
||||
mockValidatorFileParser.verify();
|
||||
List<Validator> validators = actionValidatorManager.getValidators(DataAware2.class, alias);
|
||||
|
||||
assertThat(validators).hasSize(3).map(Validator::getClass).containsExactly(
|
||||
RequiredFieldValidator.class,
|
||||
RequiredStringValidator.class,
|
||||
RequiredStringValidator.class
|
||||
);
|
||||
assertThat(validators).hasSize(3).map(Validator::getValidatorType).containsExactly(
|
||||
"required",
|
||||
"requiredstring",
|
||||
"requiredstring"
|
||||
);
|
||||
assertThat(validators).hasSize(3).map(Validator::getDefaultMessage).containsExactly(
|
||||
"You must enter a value for data.",
|
||||
"You must enter a value for data.",
|
||||
"You must enter a value for data."
|
||||
);
|
||||
}
|
||||
|
||||
public void testGetValidatorsFromInterface() {
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/DataAware-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
actionValidatorManager.getValidators(SimpleAction3.class, alias);
|
||||
mockValidatorFileParser.verify();
|
||||
}
|
||||
List<Validator> validators = actionValidatorManager.getValidators(SimpleAction3.class, alias);
|
||||
|
||||
public void testSameAliasWithDifferentClass() {
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/SimpleAction-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validation.xml")),
|
||||
new ArrayList());
|
||||
mockValidatorFileParser.expectAndReturn("parseActionValidatorConfigs",
|
||||
C.args(C.IS_NOT_NULL, C.IS_NOT_NULL, C.eq("com/opensymphony/xwork2/test/SimpleAction2-validationAlias-validation.xml")),
|
||||
new ArrayList());
|
||||
actionValidatorManager.getValidators(SimpleAction.class, alias);
|
||||
actionValidatorManager.getValidators(SimpleAction2.class, alias);
|
||||
mockValidatorFileParser.verify();
|
||||
assertThat(validators).hasSize(13).map(Validator::getClass).containsExactly(
|
||||
ExpressionValidator.class,
|
||||
RequiredFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
DoubleRangeFieldValidator.class,
|
||||
DateRangeFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
LongRangeFieldValidator.class,
|
||||
ShortRangeFieldValidator.class,
|
||||
RequiredFieldValidator.class,
|
||||
IntRangeFieldValidator.class,
|
||||
RequiredFieldValidator.class,
|
||||
RequiredStringValidator.class
|
||||
);
|
||||
assertThat(validators).hasSize(13).map(Validator::getValidatorType).containsExactly(
|
||||
"expression",
|
||||
"required",
|
||||
"int",
|
||||
"double",
|
||||
"date",
|
||||
"int",
|
||||
"int",
|
||||
"long",
|
||||
"short",
|
||||
"required",
|
||||
"int",
|
||||
"required",
|
||||
"requiredstring"
|
||||
);
|
||||
assertThat(validators).hasSize(13).map(Validator::getDefaultMessage).containsExactly(
|
||||
"Foo must be greater than Bar. Foo = ${foo}, Bar = ${bar}.",
|
||||
"You must enter a value for bar.",
|
||||
"bar must be between ${min} and ${max}, current value is ${bar}.",
|
||||
"percentage must be between ${minExclusive} and ${maxExclusive}, current value is ${percentage}.",
|
||||
"The date must be between 12-22-2002 and 12-25-2002.",
|
||||
"Could not find foo.range!",
|
||||
"Could not find baz.range!",
|
||||
"Could not find foo.range!",
|
||||
"Could not find foo.range!",
|
||||
"You must enter a value for baz.",
|
||||
"baz out of range.",
|
||||
"You must enter a value for data.",
|
||||
"You must enter a value for data."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to verify WW-3850.
|
||||
*
|
||||
* @since 2.3.5
|
||||
*/
|
||||
public void testBuildsValidatorsForClassError() {
|
||||
// for this test we need to have a file manager with reloadingConfigs to true
|
||||
@@ -188,12 +194,8 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: this all need to be converted to real unit tests
|
||||
|
||||
public void testSkipUserMarkerActionLevelShortCircuit() {
|
||||
// get validators
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
assertEquals(10, validatorList.size());
|
||||
|
||||
try {
|
||||
@@ -202,16 +204,17 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
user.setEmail("bad_email");
|
||||
user.setEmail2("bad_email");
|
||||
|
||||
ValidatorContext context = new GenericValidatorContext(user);
|
||||
ValidationAware validationAware = new SimpleAction();
|
||||
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
|
||||
actionValidatorManager.validate(user, null, context);
|
||||
assertTrue(context.hasFieldErrors());
|
||||
|
||||
// check field errors
|
||||
List l = (List) context.getFieldErrors().get("email");
|
||||
List<String> l = context.getFieldErrors().get("email");
|
||||
assertNotNull(l);
|
||||
assertEquals(1, l.size());
|
||||
assertEquals("Not a valid e-mail.", l.get(0));
|
||||
l = (List) context.getFieldErrors().get("email2");
|
||||
l = context.getFieldErrors().get("email2");
|
||||
assertNotNull(l);
|
||||
assertEquals(2, l.size());
|
||||
assertEquals("Not a valid e-mail2.", l.get(0));
|
||||
@@ -219,19 +222,17 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
|
||||
// check action errors
|
||||
assertTrue(context.hasActionErrors());
|
||||
l = (List) context.getActionErrors();
|
||||
l = new ArrayList<>(context.getActionErrors());
|
||||
assertNotNull(l);
|
||||
assertEquals(2, l.size()); // both expression test failed see User-validation.xml
|
||||
assertEquals("Email does not start with mark", l.get(0));
|
||||
} catch (ValidationException ex) {
|
||||
ex.printStackTrace();
|
||||
fail("Validation error: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testSkipAllActionLevelShortCircuit2() {
|
||||
// get validators
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
assertEquals(10, validatorList.size());
|
||||
|
||||
try {
|
||||
@@ -244,34 +245,30 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
user.setEmail("mark_bad_email_for_field_val@foo.com");
|
||||
user.setEmail2("mark_bad_email_for_field_val@foo.com");
|
||||
|
||||
ValidatorContext context = new GenericValidatorContext(user);
|
||||
ValidationAware validationAware = new SimpleAction();
|
||||
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
|
||||
actionValidatorManager.validate(user, null, context);
|
||||
assertTrue(context.hasFieldErrors());
|
||||
|
||||
// check field errors
|
||||
// we have an error in this field level, email does not ends with mycompany.com
|
||||
List l = (List) context.getFieldErrors().get("email");
|
||||
List<String> l = context.getFieldErrors().get("email");
|
||||
assertNotNull(l);
|
||||
assertEquals(1, l.size()); // because email-field-val is short-circuit
|
||||
assertEquals("Email not from the right company.", l.get(0));
|
||||
|
||||
|
||||
// check action errors
|
||||
l = (List) context.getActionErrors();
|
||||
l = new ArrayList<>(context.getActionErrors());
|
||||
assertFalse(context.hasActionErrors());
|
||||
assertEquals(0, l.size());
|
||||
|
||||
|
||||
} catch (ValidationException ex) {
|
||||
ex.printStackTrace();
|
||||
fail("Validation error: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testActionLevelShortCircuit() throws Exception {
|
||||
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
assertEquals(10, validatorList.size());
|
||||
|
||||
User user = new User();
|
||||
@@ -280,18 +277,18 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
user.setEmail("tmjee(at)yahoo.co.uk");
|
||||
user.setEmail("tm_jee(at)yahoo.co.uk");
|
||||
|
||||
ValidatorContext context = new GenericValidatorContext(user);
|
||||
ValidationAware validationAware = new SimpleAction();
|
||||
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
|
||||
actionValidatorManager.validate(user, null, context);
|
||||
|
||||
// check field level errors
|
||||
// check field level errors
|
||||
// shouldn't have any because action error prevents validation of anything else
|
||||
List l = (List) context.getFieldErrors().get("email2");
|
||||
List<String> l = context.getFieldErrors().get("email2");
|
||||
assertNull(l);
|
||||
|
||||
|
||||
// check action errors
|
||||
assertTrue(context.hasActionErrors());
|
||||
l = (List) context.getActionErrors();
|
||||
l = new ArrayList<>(context.getActionErrors());
|
||||
assertNotNull(l);
|
||||
// we only get one, because UserMarker-validation.xml action-level validator
|
||||
// already sc it :-)
|
||||
@@ -299,10 +296,8 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
assertEquals("Email not the same as email2", l.get(0));
|
||||
}
|
||||
|
||||
|
||||
public void testShortCircuitNoErrors() {
|
||||
// get validators
|
||||
List validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
List<Validator> validatorList = actionValidatorManager.getValidators(User.class, null);
|
||||
assertEquals(10, validatorList.size());
|
||||
|
||||
try {
|
||||
@@ -311,73 +306,72 @@ public class DefaultActionValidatorManagerTest extends XWorkTestCase {
|
||||
user.setEmail("mark@mycompany.com");
|
||||
user.setEmail2("mark@mycompany.com");
|
||||
|
||||
ValidatorContext context = new GenericValidatorContext(user);
|
||||
ValidationAware validationAware = new SimpleAction();
|
||||
ValidatorContext context = new DelegatingValidatorContext(validationAware, actionValidatorManager.textProviderFactory);
|
||||
actionValidatorManager.validate(user, null, context);
|
||||
assertFalse(context.hasErrors());
|
||||
} catch (ValidationException ex) {
|
||||
ex.printStackTrace();
|
||||
fail("Validation error: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testFieldErrorsOrder() throws Exception {
|
||||
ValidationOrderAction action = new ValidationOrderAction();
|
||||
actionValidatorManager.validate(action, "actionContext");
|
||||
Map fieldErrors = action.getFieldErrors();
|
||||
Iterator i = fieldErrors.entrySet().iterator();
|
||||
ValidationOrderAction action = new ValidationOrderAction();
|
||||
actionValidatorManager.validate(action, "actionContext");
|
||||
Map<String, List<String>> fieldErrors = action.getFieldErrors();
|
||||
Iterator<Map.Entry<String, List<String>>> i = fieldErrors.entrySet().iterator();
|
||||
|
||||
assertNotNull(fieldErrors);
|
||||
assertEquals(fieldErrors.size(), 12);
|
||||
assertNotNull(fieldErrors);
|
||||
assertEquals(fieldErrors.size(), 12);
|
||||
|
||||
|
||||
Map.Entry e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "username");
|
||||
assertEquals(((List)e.getValue()).get(0), "username required");
|
||||
Map.Entry<String, List<String>> e = i.next();
|
||||
assertEquals(e.getKey(), "username");
|
||||
assertEquals(e.getValue().get(0), "username required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "password");
|
||||
assertEquals(((List)e.getValue()).get(0), "password required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "password");
|
||||
assertEquals((e.getValue()).get(0), "password required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "confirmPassword");
|
||||
assertEquals(((List)e.getValue()).get(0), "confirm password required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "confirmPassword");
|
||||
assertEquals((e.getValue()).get(0), "confirm password required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "firstName");
|
||||
assertEquals(((List)e.getValue()).get(0), "first name required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "firstName");
|
||||
assertEquals((e.getValue()).get(0), "first name required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "lastName");
|
||||
assertEquals(((List)e.getValue()).get(0), "last name required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "lastName");
|
||||
assertEquals((e.getValue()).get(0), "last name required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "city");
|
||||
assertEquals(((List)e.getValue()).get(0), "city is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "city");
|
||||
assertEquals((e.getValue()).get(0), "city is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "province");
|
||||
assertEquals(((List)e.getValue()).get(0), "province is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "province");
|
||||
assertEquals((e.getValue()).get(0), "province is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "country");
|
||||
assertEquals(((List)e.getValue()).get(0), "country is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "country");
|
||||
assertEquals((e.getValue()).get(0), "country is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "postalCode");
|
||||
assertEquals(((List)e.getValue()).get(0), "postal code is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "postalCode");
|
||||
assertEquals((e.getValue()).get(0), "postal code is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "email");
|
||||
assertEquals(((List)e.getValue()).get(0), "email is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "email");
|
||||
assertEquals((e.getValue()).get(0), "email is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "website");
|
||||
assertEquals(((List)e.getValue()).get(0), "website is required");
|
||||
|
||||
e = (Map.Entry) i.next();
|
||||
assertEquals(e.getKey(), "passwordHint");
|
||||
assertEquals(((List)e.getValue()).get(0), "password hint is required");
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "website");
|
||||
assertEquals((e.getValue()).get(0), "website is required");
|
||||
|
||||
e = i.next();
|
||||
assertEquals(e.getKey(), "passwordHint");
|
||||
assertEquals((e.getValue()).get(0), "password hint is required");
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import com.opensymphony.xwork2.config.entities.ActionConfig;
|
||||
import com.opensymphony.xwork2.conversion.impl.ConversionData;
|
||||
import org.easymock.EasyMock;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
@@ -142,6 +144,15 @@ public class VisitorFieldValidatorTest extends XWorkTestCase {
|
||||
assertEquals(1, errors.size());
|
||||
}
|
||||
|
||||
public void testDateValidation() throws Exception {
|
||||
action.setBirthday(Date.valueOf(LocalDate.now().minusYears(20)));
|
||||
action.setContext("birthday");
|
||||
|
||||
validate("birthday");
|
||||
|
||||
assertFalse(action.hasFieldErrors());
|
||||
}
|
||||
|
||||
public void testContextIsOverriddenByContextParamInValidationXML() throws Exception {
|
||||
validate("visitorValidationAlias");
|
||||
assertTrue(action.hasFieldErrors());
|
||||
|
||||
+10
-1
@@ -22,6 +22,7 @@ import com.opensymphony.xwork2.ActionSupport;
|
||||
import com.opensymphony.xwork2.TestBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -37,7 +38,7 @@ public class VisitorValidatorTestAction extends ActionSupport {
|
||||
private String context;
|
||||
private TestBean bean = new TestBean();
|
||||
private TestBean[] testBeanArray;
|
||||
|
||||
private Date birthday;
|
||||
|
||||
public VisitorValidatorTestAction() {
|
||||
testBeanArray = new TestBean[5];
|
||||
@@ -80,4 +81,12 @@ public class VisitorValidatorTestAction extends ActionSupport {
|
||||
public List<TestBean> getTestBeanList() {
|
||||
return testBeanList;
|
||||
}
|
||||
|
||||
public Date getBirthday() {
|
||||
return birthday;
|
||||
}
|
||||
|
||||
public void setBirthday(Date birthday) {
|
||||
this.birthday = birthday;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,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;
|
||||
@@ -588,6 +590,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 javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -127,6 +124,7 @@ 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());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.apache.struts2.interceptor;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.config.ConfigurationException;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
@@ -31,6 +32,7 @@ import org.apache.struts2.interceptor.csp.DefaultCspSettings;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
@@ -74,8 +76,10 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
public void testEnforcingCspHeadersSet() throws Exception {
|
||||
String reportUri = "/csp-reports";
|
||||
String reportTo = "csp-group";
|
||||
boolean enforcingMode = true;
|
||||
interceptor.setReportUri(reportUri);
|
||||
interceptor.setReportTo(reportTo);
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
session.setAttribute("nonce", "foo");
|
||||
|
||||
@@ -84,13 +88,15 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
assertNotNull("Nonce key does not exist", session.getAttribute("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.getAttribute("nonce")));
|
||||
assertNotEquals("New nonce value couldn't be set", "foo", session.getAttribute("nonce"));
|
||||
checkHeader(reportUri, enforcingMode);
|
||||
checkHeader(reportUri, reportTo, enforcingMode);
|
||||
}
|
||||
|
||||
public void testReportingCspHeadersSet() throws Exception {
|
||||
String reportUri = "/csp-reports";
|
||||
String reportTo = "csp-group";
|
||||
boolean enforcingMode = false;
|
||||
interceptor.setReportUri(reportUri);
|
||||
interceptor.setReportTo(reportTo);
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
session.setAttribute("nonce", "foo");
|
||||
|
||||
@@ -98,7 +104,7 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
assertNotNull("Nonce value is empty", session.getAttribute("nonce"));
|
||||
assertNotEquals("New nonce value couldn't be set", "foo", session.getAttribute("nonce"));
|
||||
checkHeader(reportUri, enforcingMode);
|
||||
checkHeader(reportUri, reportTo, enforcingMode);
|
||||
}
|
||||
|
||||
public void test_uriSetOnlyWhenSetIsCalled() throws Exception {
|
||||
@@ -174,21 +180,89 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
checkHeader("/report-uri", enforcingMode);
|
||||
}
|
||||
|
||||
public void testNonExistingCspSettingsClassName() throws Exception {
|
||||
boolean enforcingMode = true;
|
||||
mai.setAction(new TestAction());
|
||||
request.setContextPath("/app");
|
||||
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
interceptor.setReportUri("/report-uri");
|
||||
interceptor.setPrependServletContext(false);
|
||||
|
||||
try {
|
||||
interceptor.setCspSettingsClassName("foo");
|
||||
interceptor.intercept(mai);
|
||||
fail("Expected exception");
|
||||
} catch (ConfigurationException e) {
|
||||
assertEquals("The class foo doesn't exist!", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testInvalidCspSettingsClassName() throws Exception {
|
||||
boolean enforcingMode = true;
|
||||
mai.setAction(new TestAction());
|
||||
request.setContextPath("/app");
|
||||
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
interceptor.setReportUri("/report-uri");
|
||||
interceptor.setPrependServletContext(false);
|
||||
|
||||
try {
|
||||
interceptor.setCspSettingsClassName(Integer.class.getName());
|
||||
interceptor.intercept(mai);
|
||||
fail("Expected exception");
|
||||
} catch (ConfigurationException e) {
|
||||
assertEquals("The class java.lang.Integer doesn't implement org.apache.struts2.interceptor.csp.CspSettings!", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomCspSettingsClassName() throws Exception {
|
||||
boolean enforcingMode = true;
|
||||
mai.setAction(new TestAction());
|
||||
request.setContextPath("/app");
|
||||
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
interceptor.setReportUri("/report-uri");
|
||||
interceptor.setPrependServletContext(false);
|
||||
interceptor.setCspSettingsClassName(CustomDefaultCspSettings.class.getName());
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
String header = response.getHeader(CspSettings.CSP_ENFORCE_HEADER);
|
||||
|
||||
// no other customization matters for this particular class
|
||||
assertEquals("foo", header);
|
||||
}
|
||||
|
||||
public void checkHeader(String reportUri, boolean enforcingMode) {
|
||||
checkHeader(reportUri, null, enforcingMode);
|
||||
}
|
||||
|
||||
public void checkHeader(String reportUri, String reportTo, boolean enforcingMode) {
|
||||
String expectedCspHeader;
|
||||
if (Strings.isEmpty(reportUri)) {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; ",
|
||||
CspSettings.OBJECT_SRC, CspSettings.NONE,
|
||||
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
|
||||
CspSettings.BASE_URI, CspSettings.NONE
|
||||
CspSettings.OBJECT_SRC, CspSettings.NONE,
|
||||
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
|
||||
CspSettings.BASE_URI, CspSettings.NONE
|
||||
);
|
||||
} else {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s",
|
||||
CspSettings.OBJECT_SRC, CspSettings.NONE,
|
||||
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
|
||||
CspSettings.BASE_URI, CspSettings.NONE,
|
||||
CspSettings.REPORT_URI, reportUri
|
||||
);
|
||||
if (Strings.isEmpty(reportTo)) {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s; ",
|
||||
CspSettings.OBJECT_SRC, CspSettings.NONE,
|
||||
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
|
||||
CspSettings.BASE_URI, CspSettings.NONE,
|
||||
CspSettings.REPORT_URI, reportUri
|
||||
);
|
||||
} else {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s; %s %s; ",
|
||||
CspSettings.OBJECT_SRC, CspSettings.NONE,
|
||||
CspSettings.SCRIPT_SRC, session.getAttribute("nonce"), CspSettings.STRICT_DYNAMIC, CspSettings.HTTP, CspSettings.HTTPS,
|
||||
CspSettings.BASE_URI, CspSettings.NONE,
|
||||
CspSettings.REPORT_URI, reportUri,
|
||||
CspSettings.REPORT_TO, reportTo
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String header;
|
||||
@@ -207,10 +281,11 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
super.setUp();
|
||||
container.inject(interceptor);
|
||||
ActionContext context = ActionContext.getContext()
|
||||
.withServletRequest(request)
|
||||
.withServletResponse(response)
|
||||
.withSession(new SessionMap(request))
|
||||
.bind();
|
||||
.withContainer(container)
|
||||
.withServletRequest(request)
|
||||
.withServletResponse(response)
|
||||
.withSession(new SessionMap(request))
|
||||
.bind();
|
||||
mai.setInvocationContext(context);
|
||||
session = request.getSession();
|
||||
}
|
||||
@@ -230,4 +305,15 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom DefaultCspSettings class that overrides the createPolicyFormat method
|
||||
* to return a fixed value.
|
||||
*/
|
||||
public static class CustomDefaultCspSettings extends DefaultCspSettings {
|
||||
|
||||
protected String createPolicyFormat(HttpServletRequest request) {
|
||||
return "foo";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,26 @@ public class I18nInterceptorTest extends TestCase {
|
||||
assertEquals(Locale.getDefault(), session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
|
||||
}
|
||||
|
||||
public void testTrimableLocaleString1() throws Exception {
|
||||
prepare(I18nInterceptor.DEFAULT_PARAMETER, "de\n");
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
|
||||
assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
|
||||
assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
|
||||
}
|
||||
|
||||
public void testTrimableLocaleString2() throws Exception {
|
||||
prepare(I18nInterceptor.DEFAULT_PARAMETER, "de ");
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertFalse(mai.getInvocationContext().getParameters().get(I18nInterceptor.DEFAULT_PARAMETER).isDefined()); // should have been removed
|
||||
assertNotNull(session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should be stored here
|
||||
assertEquals(Locale.GERMAN, session.get(I18nInterceptor.DEFAULT_SESSION_ATTRIBUTE)); // should create a locale object
|
||||
}
|
||||
|
||||
public void testWithVariant() throws Exception {
|
||||
prepare(I18nInterceptor.DEFAULT_PARAMETER, "ja_JP_JP");
|
||||
interceptor.intercept(mai);
|
||||
|
||||
+54
-6
@@ -26,6 +26,7 @@ import org.apache.struts2.StrutsInternalTestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.NotSerializableException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.security.SecureRandom;
|
||||
@@ -41,6 +42,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
/**
|
||||
* Test case for BackgroundProcessTest.
|
||||
*/
|
||||
@@ -59,9 +62,9 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
|
||||
invocation.setInvocationContext(ActionContext.getContext());
|
||||
|
||||
StrutsBackgroundProcess bp = (StrutsBackgroundProcess) new StrutsBackgroundProcess(
|
||||
invocation,
|
||||
"BackgroundProcessTest.testSerializeDeserialize",
|
||||
Thread.MIN_PRIORITY
|
||||
invocation,
|
||||
"BackgroundProcessTest.testSerializeDeserialize",
|
||||
Thread.MIN_PRIORITY
|
||||
).prepare();
|
||||
executor.execute(bp);
|
||||
|
||||
@@ -120,6 +123,31 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
|
||||
assertEquals(100, mutableState.get());
|
||||
}
|
||||
|
||||
public void testErrorableProcesses1() {
|
||||
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> {
|
||||
throw new IllegalStateException("boom");
|
||||
});
|
||||
|
||||
BackgroundProcess bp = new ErrorableBackgroundProcess(invocation, null).prepare();
|
||||
executor.execute(bp);
|
||||
|
||||
await().atLeast(100, TimeUnit.MILLISECONDS).until(bp::isDone);
|
||||
|
||||
assertTrue("afterInvocation not called in case of exception", ((ErrorableBackgroundProcess) bp).isDoneAfter());
|
||||
}
|
||||
|
||||
public void testErrorableProcesses2() {
|
||||
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> "done");
|
||||
|
||||
IllegalStateException expected = new IllegalStateException("after!");
|
||||
BackgroundProcess bp = new ErrorableBackgroundProcess(invocation, expected).prepare();
|
||||
executor.execute(bp);
|
||||
|
||||
await().atLeast(100, TimeUnit.MILLISECONDS).until(bp::isDone);
|
||||
|
||||
assertEquals(expected, bp.getException());
|
||||
}
|
||||
|
||||
public void testUnpreparedProcess() throws ExecutionException, InterruptedException, TimeoutException {
|
||||
// given
|
||||
MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> "done");
|
||||
@@ -147,7 +175,8 @@ public class StrutsBackgroundProcessTest extends StrutsInternalTestCase {
|
||||
}
|
||||
|
||||
private static class NotSerializableException extends Exception {
|
||||
private MockHttpServletRequest notSerializableField;
|
||||
@SuppressWarnings("unused")
|
||||
private final MockHttpServletRequest notSerializableField;
|
||||
|
||||
NotSerializableException(MockHttpServletRequest notSerializableField) {
|
||||
this.notSerializableField = notSerializableField;
|
||||
@@ -170,10 +199,29 @@ class LockBackgroundProcess extends StrutsBackgroundProcess {
|
||||
super.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorableBackgroundProcess extends StrutsBackgroundProcess {
|
||||
|
||||
private final Exception afterException;
|
||||
private boolean doneAfter;
|
||||
|
||||
public ErrorableBackgroundProcess(ActionInvocation invocation, Exception afterException) {
|
||||
super(invocation, "errorabale process", Thread.NORM_PRIORITY);
|
||||
this.afterException = afterException;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterInvocation() throws Exception {
|
||||
super.afterInvocation();
|
||||
lock.notify();
|
||||
if (afterException != null) {
|
||||
throw afterException;
|
||||
} else {
|
||||
super.afterInvocation();
|
||||
doneAfter = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDoneAfter() {
|
||||
return doneAfter;
|
||||
}
|
||||
}
|
||||
|
||||
+35
-26
@@ -116,15 +116,17 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
pi.setParameters(action, vs, HttpParameters.create(params).build());
|
||||
|
||||
// then
|
||||
assertEquals(3, action.getActionMessages().size());
|
||||
assertEquals(3, action.getActionErrors().size());
|
||||
|
||||
String msg1 = action.getActionMessage(0);
|
||||
String msg2 = action.getActionMessage(1);
|
||||
String msg3 = action.getActionMessage(2);
|
||||
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
|
||||
|
||||
assertEquals("Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
|
||||
assertEquals("Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
|
||||
assertEquals("Error setting expression 'top['name'](0)' with value 'true'", msg3);
|
||||
String msg1 = actionErrors.get(0);
|
||||
String msg2 = actionErrors.get(1);
|
||||
String msg3 = actionErrors.get(2);
|
||||
|
||||
assertEquals("Unexpected Exception caught setting 'expression' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'expression' with value '#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),#req=@org.apache.struts2.ServletActionContext@getRequest(),#resp=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#resp.println(#req.getRealPath('/')),#resp.close()'", msg1);
|
||||
assertEquals("Unexpected Exception caught setting 'name' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'name' with value '(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false), #_memberAccess[\"allowStaticMethodAccess\"]= new java.lang.Boolean(true), @java.lang.Runtime@getRuntime().exec('mkdir /tmp/PWNAGE'))(meh)'", msg2);
|
||||
assertEquals("Unexpected Exception caught setting 'top['name'](0)' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'top['name'](0)' with value 'true'", msg3);
|
||||
assertNull(action.getName());
|
||||
}
|
||||
|
||||
@@ -201,15 +203,16 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
pi.setParameters(action, vs, HttpParameters.create(params).build());
|
||||
|
||||
// then
|
||||
assertEquals(3, action.getActionMessages().size());
|
||||
assertEquals(3, action.getActionErrors().size());
|
||||
|
||||
String msg1 = action.getActionMessage(0);
|
||||
String msg2 = action.getActionMessage(1);
|
||||
String msg3 = action.getActionMessage(2);
|
||||
List<String> actionErrors = new ArrayList<>(action.getActionErrors());
|
||||
String msg1 = actionErrors.get(0);
|
||||
String msg2 = actionErrors.get(1);
|
||||
String msg3 = actionErrors.get(2);
|
||||
|
||||
assertEquals("Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
|
||||
assertEquals("Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
|
||||
assertEquals("Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
|
||||
assertEquals("Unexpected Exception caught setting 'class.classLoader.defaultAssertionStatus' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.defaultAssertionStatus' with value 'true'", msg1);
|
||||
assertEquals("Unexpected Exception caught setting 'class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'class.classLoader.jarPath' with value 'bad'", msg2);
|
||||
assertEquals("Unexpected Exception caught setting 'model.class.classLoader.jarPath' on 'class org.apache.struts2.interceptor.parameter.ValidateAction: Error setting expression 'model.class.classLoader.jarPath' with value 'very bad'", msg3);
|
||||
|
||||
assertFalse(excluded.get(pollution1));
|
||||
assertFalse(excluded.get(pollution2));
|
||||
@@ -582,8 +585,8 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
container.inject(config.getInterceptors().get(0).getInterceptor());
|
||||
ActionProxy proxy = actionProxyFactory.createActionProxy("", MockConfigurationProvider.PARAM_INTERCEPTOR_ACTION_NAME, null, extraContext.getContextMap());
|
||||
proxy.execute();
|
||||
final String actionMessage = "" + ((SimpleAction) proxy.getAction()).getActionMessages().toArray()[0];
|
||||
assertTrue(actionMessage.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
|
||||
final String actionError = "" + ((SimpleAction) proxy.getAction()).getActionErrors().toArray()[0];
|
||||
assertTrue(actionError.contains("Error setting expression 'not_a_property' with value 'There is no action property named like this'"));
|
||||
}
|
||||
|
||||
public void testNonexistentParametersAreIgnoredInProductionMode() throws Exception {
|
||||
@@ -1014,59 +1017,65 @@ public class ParametersInterceptorTest extends XWorkTestCase {
|
||||
class ValidateAction implements ValidationAware {
|
||||
|
||||
private final List<String> messages = new LinkedList<>();
|
||||
private final List<String> errors = new LinkedList<>();
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public void setActionErrors(Collection<String> errorMessages) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getActionErrors() {
|
||||
return null;
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActionMessages(Collection<String> messages) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getActionMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFieldErrors(Map<String, List<String>> errorMap) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getFieldErrors() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addActionError(String anErrorMessage) {
|
||||
errors.add(anErrorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addActionMessage(String aMessage) {
|
||||
messages.add(aMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addFieldError(String fieldName, String errorMessage) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasActionErrors() {
|
||||
return false;
|
||||
return !errors.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasActionMessages() {
|
||||
return !messages.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasErrors() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFieldErrors() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getActionMessage(int index) {
|
||||
return messages.get(index);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,14 @@ public class StrutsQueryStringParserTest {
|
||||
assertEquals("test", queryParameters.getQueryFragment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleOnlyFragment() {
|
||||
QueryStringParser.Result queryParameters = parser.parse("#test");
|
||||
|
||||
assertTrue(queryParameters.getQueryParams().isEmpty());
|
||||
assertEquals("test", queryParameters.getQueryFragment());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.parser = new StrutsQueryStringParser(new StrutsUrlDecoder());
|
||||
|
||||
@@ -22,14 +22,10 @@ import com.mockobjects.servlet.MockJspWriter;
|
||||
import java.io.IOException;
|
||||
import javax.servlet.jsp.JspException;
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
public class SetTagTest extends AbstractUITagTest {
|
||||
|
||||
Chewbacca chewie;
|
||||
SetTag tag;
|
||||
|
||||
private Chewbacca chewie;
|
||||
private SetTag tag;
|
||||
|
||||
public void testApplicationScope() throws JspException {
|
||||
tag.setName("foo");
|
||||
@@ -397,6 +393,50 @@ public class SetTagTest extends AbstractUITagTest {
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testShortVarNameInPageScope() throws JspException {
|
||||
tag.setName("f");
|
||||
tag.setValue("name");
|
||||
tag.setScope("page");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("chewie", pageContext.getAttribute("f"));
|
||||
}
|
||||
|
||||
public void testShortVarNameInRequestScope() throws JspException {
|
||||
tag.setName("f");
|
||||
tag.setValue("name");
|
||||
tag.setScope("request");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("chewie", request.getAttribute("f"));
|
||||
}
|
||||
|
||||
public void testShortVarNameInSessionScope() throws JspException {
|
||||
tag.setName("f");
|
||||
tag.setValue("name");
|
||||
tag.setScope("session");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("chewie", session.get("f"));
|
||||
}
|
||||
|
||||
public void testShortVarNameInApplicationScope() throws JspException {
|
||||
tag.setName("f");
|
||||
tag.setValue("name");
|
||||
tag.setScope("application");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("chewie", servletContext.getAttribute("f"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
@@ -408,9 +448,9 @@ public class SetTagTest extends AbstractUITagTest {
|
||||
}
|
||||
|
||||
|
||||
public class Chewbacca {
|
||||
String name;
|
||||
boolean furry;
|
||||
public static class Chewbacca {
|
||||
private String name;
|
||||
private boolean furry;
|
||||
|
||||
public Chewbacca(String name, boolean furry) {
|
||||
this.name = name;
|
||||
|
||||
@@ -2092,6 +2092,42 @@ public class URLTagTest extends AbstractUITagTest {
|
||||
strutsBodyTagsAreReflectionEqual(tag, freshTag));
|
||||
}
|
||||
|
||||
public void testQueryParamsAndFragment() throws Exception {
|
||||
request.setRequestURI("/public/about");
|
||||
tag.setAction("company");
|
||||
tag.setValue("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false");
|
||||
tag.setEscapeAmp("false");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("/books?hl=en&lr=Y&redir_esc=y#v=twopage&q&f=false", writer.toString());
|
||||
}
|
||||
|
||||
public void testDoubleEqualSigns() throws Exception {
|
||||
request.setRequestURI("/public/about");
|
||||
tag.setAction("company");
|
||||
tag.setValue("/PublicationsDetail.aspx?ID=GjTu91suYQI=&t=1");
|
||||
tag.setEscapeAmp("false");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("/PublicationsDetail.aspx?ID=GjTu91suYQI%3D&t=1", writer.toString());
|
||||
}
|
||||
|
||||
public void testOnlyFragment() throws Exception {
|
||||
request.setRequestURI("/public/about");
|
||||
tag.setAction("company");
|
||||
tag.setValue("/books#v=twopage&q&f=false");
|
||||
tag.setEscapeAmp("false");
|
||||
|
||||
tag.doStartTag();
|
||||
tag.doEndTag();
|
||||
|
||||
assertEquals("/books#v=twopage&q&f=false", writer.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
@@ -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>
|
||||
+8
@@ -26,4 +26,12 @@
|
||||
<message>You must enter a context.</message>
|
||||
</field-validator>
|
||||
</field>
|
||||
<field name="birthday">
|
||||
<field-validator type="fieldexpression">
|
||||
<param name="expression"><![CDATA[
|
||||
(birthday == null || birthday.before(new java.util.Date()))
|
||||
]]></param>
|
||||
<message key="errors_birthday" />
|
||||
</field-validator>
|
||||
</field>
|
||||
</validators>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-async-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-cdi-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-config-browser-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-convention-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-dwr-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-embeddedjsp-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-gxp-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jasperreports-plugin</artifactId>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-javatemplates-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-jfreechart-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-json-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-junit-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-osgi-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-oval-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-pell-multipart-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-plexus-plugin</artifactId>
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-parent</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-junit-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-mocks-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-tiles-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-portlet-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-rest-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-sitemesh-plugin</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>6.4.0</version>
|
||||
<version>6.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-spring-plugin</artifactId>
|
||||
|
||||
+49
-27
@@ -19,50 +19,72 @@
|
||||
package com.opensymphony.xwork2.ognl;
|
||||
|
||||
import com.opensymphony.xwork2.ActionProxy;
|
||||
import com.opensymphony.xwork2.XWorkTestCase;
|
||||
import com.opensymphony.xwork2.XWorkJUnit4TestCase;
|
||||
import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
|
||||
import org.apache.struts2.config.StrutsXmlConfigurationProvider;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SecurityMemberAccessProxyTest extends XWorkTestCase {
|
||||
private Map<String, Object> context;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SecurityMemberAccessProxyTest extends XWorkJUnit4TestCase {
|
||||
|
||||
private static final String PROXY_MEMBER_METHOD = "isExposeProxy";
|
||||
private static final String TEST_SUB_BEAN_CLASS_METHOD = "getIssueId";
|
||||
|
||||
private Map<String, Object> context;
|
||||
private ActionProxy proxy;
|
||||
private final SecurityMemberAccess sma = new SecurityMemberAccess(null, null);
|
||||
|
||||
private Member proxyObjectProxyMember;
|
||||
private Member proxyObjectNonProxyMember;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml");
|
||||
loadConfigurationProviders(provider);
|
||||
|
||||
context = new HashMap<>();
|
||||
// Set up XWork
|
||||
XmlConfigurationProvider provider = new StrutsXmlConfigurationProvider("com/opensymphony/xwork2/spring/actionContext-xwork.xml");
|
||||
container.inject(provider);
|
||||
loadConfigurationProviders(provider);
|
||||
proxy = actionProxyFactory.createActionProxy(null, "chaintoAOPedTestSubBeanAction", null, context);
|
||||
proxyObjectProxyMember = proxy.getAction().getClass().getMethod(PROXY_MEMBER_METHOD);
|
||||
proxyObjectNonProxyMember = proxy.getAction().getClass().getMethod(TEST_SUB_BEAN_CLASS_METHOD);
|
||||
}
|
||||
|
||||
public void testProxyAccessIsBlocked() throws Exception {
|
||||
ActionProxy proxy = actionProxyFactory.createActionProxy(null,
|
||||
"chaintoAOPedTestSubBeanAction", null, context);
|
||||
/**
|
||||
* When {@code disallowProxyObjectAccess} is {@code true}, proxy access is blocked irrespective of
|
||||
* {@code disallowProxyMemberAccess} value and irrespective of whether the member itself originates from the proxy.
|
||||
*/
|
||||
@Test
|
||||
public void disallowProxyObjectAccess() {
|
||||
sma.useDisallowProxyObjectAccess(Boolean.TRUE.toString());
|
||||
Arrays.asList(proxyObjectProxyMember, proxyObjectNonProxyMember).forEach(member ->
|
||||
Arrays.asList(Boolean.TRUE, Boolean.FALSE).forEach(disallowProxyMemberAccess -> {
|
||||
sma.useDisallowProxyMemberAccess(disallowProxyMemberAccess.toString());
|
||||
assertFalse(sma.isAccessible(context, proxy.getAction(), member, ""));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(true);
|
||||
@Test
|
||||
public void disallowProxyMemberAccess() {
|
||||
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(), proxyObjectProxyMember, ""));
|
||||
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
|
||||
}
|
||||
|
||||
public void testProxyAccessIsAccessible() throws Exception {
|
||||
ActionProxy proxy = actionProxyFactory.createActionProxy(null,
|
||||
"chaintoAOPedTestSubBeanAction", null, context);
|
||||
|
||||
SecurityMemberAccess sma = new SecurityMemberAccess(true);
|
||||
|
||||
Member member = proxy.getAction().getClass().getMethod("isExposeProxy");
|
||||
|
||||
boolean accessible = sma.isAccessible(context, proxy.getAction(), member, "");
|
||||
assertTrue(accessible);
|
||||
@Test
|
||||
public void allowAllProxyAccess() {
|
||||
sma.useDisallowProxyObjectAccess(Boolean.FALSE.toString());
|
||||
sma.useDisallowProxyMemberAccess(Boolean.FALSE.toString());
|
||||
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectProxyMember, ""));
|
||||
assertTrue(sma.isAccessible(context, proxy.getAction(), proxyObjectNonProxyMember, ""));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user