Merge pull request #428 from JCgH4164838Gh792C124B5/WW-5083_Followup
WW-5083 PR#426 follow-up.
diff --git a/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
index 753b0f0..9535b3a 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/FetchMetadataInterceptor.java
@@ -21,12 +21,13 @@
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_DEST_HEADER;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_MODE_HEADER;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_SITE_HEADER;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_USER_HEADER;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.VARY_HEADER;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
-import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.TextParseUtil;
import java.util.HashSet;
import java.util.Set;
@@ -41,17 +42,19 @@
* filter the requests allowed to be processed.
*
* @see <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>
+ * @see <a href="https://www.w3.org/TR/fetch-metadata/">https://www.w3.org/TR/fetch-metadata/</a>
**/
public class FetchMetadataInterceptor extends AbstractInterceptor {
- private static final Logger logger = LogManager.getLogger(FetchMetadataInterceptor.class);
- private static final String VARY_HEADER_VALUE = String.format("%s,%s,%s", SEC_FETCH_DEST_HEADER, SEC_FETCH_SITE_HEADER, SEC_FETCH_MODE_HEADER);
+ private static final Logger LOG = LogManager.getLogger(FetchMetadataInterceptor.class);
+ private static final String VARY_HEADER_VALUE = String.format("%s,%s,%s,%s", SEC_FETCH_DEST_HEADER, SEC_FETCH_MODE_HEADER, SEC_FETCH_SITE_HEADER, SEC_FETCH_USER_HEADER);
private static final String SC_FORBIDDEN = String.valueOf(HttpServletResponse.SC_FORBIDDEN);
private final Set<String> exemptedPaths = new HashSet<>();
private final ResourceIsolationPolicy resourceIsolationPolicy = new StrutsResourceIsolationPolicy();
- public void setExemptedPaths(String paths){
+ @Inject (required=false)
+ public void setExemptedPaths(String paths) {
this.exemptedPaths.addAll(TextParseUtil.commaDelimitedStringToSet(paths));
}
@@ -73,15 +76,31 @@
return invocation.invoke();
}
- logger.atDebug().log(
- "Fetch metadata rejected cross-origin request to %s",
- contextPath
- );
+ LOG.info("Fetch metadata rejected cross-origin request to [{}]", contextPath);
return SC_FORBIDDEN;
}
+ /**
+ * Sets {@link SEC_FETCH_DEST_HEADER}, {@link SEC_FETCH_MODE_HEADER}, {@link SEC_FETCH_SITE_HEADER}, and {@link SEC_FETCH_USER_HEADER}
+ * elements in the provided ActionInvocation's HttpServletResponse {@link VARY_HEADER} response header.
+ *
+ * Note: This method will replace any previous Vary header content already set for the response.
+ * Note: In order to be effective, the Vary header modification must take place at (or very near) the start of this interceptor's processing.
+ *
+ * @param invocation Supplies the HttpServletResponse (if present) to which the SEC_FETCH_* header names are be added to its {@link VARY_HEADER} response header.
+ */
private void addVaryHeaders(ActionInvocation invocation) {
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
- response.setHeader(VARY_HEADER, VARY_HEADER_VALUE);
+ if (response != null) {
+ // TODO: Whenever servlet 3.x becomes the baseline for Struts, consider revising this method to use
+ // getHeader(VARY_HEADER) and preserve any VARY_HEADER content already set in the response.
+ // This will probably require some tokenization logic for the header contents.
+ if (LOG.isDebugEnabled() && response.containsHeader(VARY_HEADER)) {
+ LOG.debug("HTTP response already has a [{}] header set, the old value will be overwritten (replaced)", VARY_HEADER);
+ }
+ response.setHeader(VARY_HEADER, VARY_HEADER_VALUE);
+ } else {
+ LOG.debug("HTTP response is null, cannot add a new [{}] header", VARY_HEADER);
+ }
}
}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/ResourceIsolationPolicy.java b/core/src/main/java/org/apache/struts2/interceptor/ResourceIsolationPolicy.java
index b272914..305b971 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/ResourceIsolationPolicy.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/ResourceIsolationPolicy.java
@@ -30,24 +30,49 @@
* See {@link StrutsResourceIsolationPolicy} for the default implementation used.
*
* @see <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>
+ * @see <a href="https://www.w3.org/TR/fetch-metadata/">https://www.w3.org/TR/fetch-metadata/</a>
**/
@FunctionalInterface
public interface ResourceIsolationPolicy {
- String SEC_FETCH_SITE_HEADER = "sec-fetch-site";
- String SEC_FETCH_MODE_HEADER = "sec-fetch-mode";
- String SEC_FETCH_DEST_HEADER = "sec-fetch-dest";
+ String SEC_FETCH_DEST_HEADER = "Sec-Fetch-Dest";
+ String SEC_FETCH_MODE_HEADER = "Sec-Fetch-Mode";
+ String SEC_FETCH_SITE_HEADER = "Sec-Fetch-Site";
+ String SEC_FETCH_USER_HEADER = "Sec-Fetch-User";
String VARY_HEADER = "Vary";
- String SAME_ORIGIN = "same-origin";
- String SAME_SITE = "same-site";
- String NONE = "none";
- String MODE_NAVIGATE = "navigate";
- String DEST_OBJECT = "object";
+ // Valid values for the SEC_FETCH_DEST_HEADER. Note: The specifications says servers should ignore the header if it contains an invalid value.
+ String DEST_AUDIO = "audio";
+ String DEST_AUDIOWORKLET = "audioworklet";
+ String DEST_DOCUMENT = "document";
String DEST_EMBED = "embed";
- String CROSS_SITE = "cross-site";
- String CORS = "cors";
- String DEST_SCRIPT = "script";
+ String DEST_EMPTY = "empty";
+ String DEST_FONT = "font";
String DEST_IMAGE = "image";
+ String DEST_MANIFEST = "manifest";
+ String DEST_NESTED_DOCUMENT = "nested-document";
+ String DEST_OBJECT = "object";
+ String DEST_PAINTWORKLET = "paintworklet";
+ String DEST_REPORT = "report";
+ String DEST_SCRIPT = "script";
+ String DEST_SERVICEWORKER = "serviceworker";
+ String DEST_SHAREDWORKER = "sharedworker";
+ String DEST_STYLE = "style";
+ String DEST_TRACK = "track";
+ String DEST_VIDEO = "video";
+ String DEST_WORKER = "worker";
+ String DEST_XSLT = "xslt";
+ // Valid values for the SEC_FETCH_MODE_HEADER. Note: The specifications says servers should ignore the header if it contains an invalid value.
+ String MODE_CORS = "cors";
+ String MODE_NAVIGATE = "navigate";
+ String MODE_NESTED_NAVIGATE = "nested-navigate";
+ String MODE_NO_CORS = "no-cors";
+ String MODE_SAME_ORIGIN = "same-origin";
+ String MODE_WEBSOCKET = "websocket";
+ // Valid values for the SEC_FETCH_SITE_HEADER. Note: The specifications says servers should ignore the header if it contains an invalid value.
+ String SITE_CROSS_SITE = "cross-site";
+ String SITE_SAME_ORIGIN = "same-origin";
+ String SITE_SAME_SITE = "same-site";
+ String SITE_NONE = "none";
boolean isRequestAllowed(HttpServletRequest request);
}
diff --git a/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java b/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
index ed0281b..24e0e7f 100644
--- a/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
+++ b/core/src/main/java/org/apache/struts2/interceptor/StrutsResourceIsolationPolicy.java
@@ -29,6 +29,7 @@
* <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>.
*
* @see <a href="https://web.dev/fetch-metadata/">https://web.dev/fetch-metadata/</a>
+ * @see <a href="https://www.w3.org/TR/fetch-metadata/">https://www.w3.org/TR/fetch-metadata/</a>
**/
public final class StrutsResourceIsolationPolicy implements ResourceIsolationPolicy {
@@ -38,12 +39,12 @@
String site = request.getHeader(SEC_FETCH_SITE_HEADER);
// Allow requests from browsers which don't send Fetch Metadata
- if (Strings.isEmpty(site)){
+ if (Strings.isEmpty(site)) {
return true;
}
// Allow same-site and browser-initiated requests
- if (SAME_ORIGIN.equals(site) || SAME_SITE.equals(site) || NONE.equals(site)) {
+ if (SITE_SAME_ORIGIN.equalsIgnoreCase(site) || SITE_SAME_SITE.equalsIgnoreCase(site) || SITE_NONE.equalsIgnoreCase(site)) {
return true;
}
@@ -55,8 +56,8 @@
String mode = request.getHeader(SEC_FETCH_MODE_HEADER);
String dest = request.getHeader(SEC_FETCH_DEST_HEADER);
- boolean isSimpleTopLevelNavigation = MODE_NAVIGATE.equals(mode) || "GET".equals(request.getMethod());
- boolean isNotObjectOrEmbedRequest = !DEST_EMBED.equals(dest) && !DEST_OBJECT.equals(dest);
+ boolean isSimpleTopLevelNavigation = MODE_NAVIGATE.equalsIgnoreCase(mode) || "GET".equalsIgnoreCase(request.getMethod());
+ boolean isNotObjectOrEmbedRequest = !DEST_EMBED.equalsIgnoreCase(dest) && !DEST_OBJECT.equalsIgnoreCase(dest);
return isSimpleTopLevelNavigation && isNotObjectOrEmbedRequest;
}
diff --git a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
index 7bff2d0..2249077 100644
--- a/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
+++ b/core/src/test/java/org/apache/struts2/interceptor/FetchMetadataInterceptorTest.java
@@ -19,20 +19,37 @@
package org.apache.struts2.interceptor;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.DEST_EMBED;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.DEST_OBJECT;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.DEST_SCRIPT;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.MODE_NAVIGATE;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_DEST_HEADER;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_MODE_HEADER;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_SITE_HEADER;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SEC_FETCH_USER_HEADER;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SITE_NONE;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SITE_SAME_ORIGIN;
+import static org.apache.struts2.interceptor.ResourceIsolationPolicy.SITE_SAME_SITE;
import static org.apache.struts2.interceptor.ResourceIsolationPolicy.VARY_HEADER;
import static org.junit.Assert.assertNotEquals;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.XWorkTestCase;
+import com.opensymphony.xwork2.config.RuntimeConfiguration;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.config.entities.InterceptorMapping;
+import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
+import com.opensymphony.xwork2.config.entities.PackageConfig;
+import com.opensymphony.xwork2.config.providers.XmlConfigurationProvider;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import org.apache.struts2.ServletActionContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import javax.servlet.http.HttpServletResponse;
public class FetchMetadataInterceptorTest extends XWorkTestCase {
@@ -40,12 +57,9 @@
private final MockActionInvocation mai = new MockActionInvocation();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
- private static final String VARY_HEADER_VALUE = String.format(
- "%s,%s,%s",
- SEC_FETCH_DEST_HEADER,
- SEC_FETCH_SITE_HEADER,
- SEC_FETCH_MODE_HEADER
- );
+ private static final String ACCEPT_ENCODING_VALUE = "Accept-Encoding";
+ private static final String VARY_HEADER_VALUE = String.format("%s,%s,%s,%s", SEC_FETCH_DEST_HEADER, SEC_FETCH_MODE_HEADER, SEC_FETCH_SITE_HEADER, SEC_FETCH_USER_HEADER);
+ private static final String SC_FORBIDDEN = String.valueOf(HttpServletResponse.SC_FORBIDDEN);
@Override
protected void setUp() throws Exception {
@@ -59,59 +73,55 @@
}
public void testNoSite() throws Exception {
- request.removeHeader("sec-fetch-site");
+ request.removeHeader(SEC_FETCH_SITE_HEADER);
- assertNotEquals("Expected interceptor to accept this request", "403",
- interceptor.intercept(mai));
+ assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testValidSite() throws Exception {
- for (String header : Arrays.asList("same-origin", "same-site", "none")){
- request.addHeader("sec-fetch-site", header);
+ for (String header : Arrays.asList(SITE_SAME_ORIGIN, SITE_SAME_SITE, SITE_NONE)){
+ request.addHeader(SEC_FETCH_SITE_HEADER, header);
- assertNotEquals("Expected interceptor to accept this request", "403",
- interceptor.intercept(mai));
+ assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
}
public void testValidTopLevelNavigation() throws Exception {
- request.addHeader("sec-fetch-mode", "navigate");
- request.addHeader("sec-fetch-dest", "script");
+ request.addHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE);
+ request.addHeader(SEC_FETCH_DEST_HEADER, DEST_SCRIPT);
request.setMethod("GET");
- assertNotEquals("Expected interceptor to accept this request", "403",
- interceptor.intercept(mai));
+ assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testInvalidTopLevelNavigation() throws Exception {
- for (String header : Arrays.asList("object", "embed")) {
- request.addHeader("sec-fetch-site", "foo");
- request.addHeader("sec-fetch-mode", "navigate");
- request.addHeader("sec-fetch-dest", header);
+ for (String header : Arrays.asList(DEST_OBJECT, DEST_EMBED)) {
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
+ request.addHeader(SEC_FETCH_MODE_HEADER, MODE_NAVIGATE);
+ request.addHeader(SEC_FETCH_DEST_HEADER, header);
request.setMethod("GET");
- assertEquals("Expected interceptor to NOT accept this request", "403", interceptor.intercept(mai));
+ assertEquals("Expected interceptor to NOT accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
}
public void testPathInExemptedPaths() throws Exception {
- request.addHeader("sec-fetch-site", "foo");
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/foo");
- assertNotEquals("Expected interceptor to accept this request", "403",
- interceptor.intercept(mai));
+ assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testPathNotInExemptedPaths() throws Exception {
- request.addHeader("sec-fetch-site", "foo");
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/foobar");
- assertEquals("Expected interceptor to NOT accept this request", "403", interceptor.intercept(mai));
+ assertEquals("Expected interceptor to NOT accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testVaryHeaderAcceptedReq() throws Exception {
- request.addHeader("sec-fetch-site", "foo");
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/foo");
interceptor.intercept(mai);
@@ -121,11 +131,132 @@
}
public void testVaryHeaderRejectedReq() throws Exception {
- request.addHeader("sec-fetch-site", "foo");
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
interceptor.intercept(mai);
assertTrue("Expected vary header to be included", response.containsHeader(VARY_HEADER));
assertEquals("Expected different vary header value", response.getHeader(VARY_HEADER), VARY_HEADER_VALUE);
}
+
+ public void testVaryHeaderReplaced() throws Exception {
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
+ response.addHeader(VARY_HEADER, ACCEPT_ENCODING_VALUE); // Simulate Vary header present due to processing before this interceptor.
+ assertEquals("Initial vary response header addition failed ?", response.getHeader(VARY_HEADER), ACCEPT_ENCODING_VALUE);
+
+ interceptor.intercept(mai);
+
+ assertTrue("Expected vary header to be included", response.containsHeader(VARY_HEADER));
+ assertFalse("Expected original vary header content to be replaced", response.getHeader(VARY_HEADER).contains(ACCEPT_ENCODING_VALUE));
+ assertTrue("Expected added vary header content to be present", response.getHeader(VARY_HEADER).contains(VARY_HEADER_VALUE));
+ }
+
+ public void testSetExemptedPathsInjectionIndirectly() throws Exception {
+ // Perform a multi-step test to confirm (indirectly) that the method parameter injection of setExemptedPaths() for
+ // the FetchMetadataInterceptor is functioning as expected, when configured appropriately.
+ // Ensure we're using the specific test configuration, not the default simple configuration.
+ XmlConfigurationProvider configurationProvider = new XmlConfigurationProvider("struts-testing.xml");
+ container.inject(configurationProvider);
+ loadConfigurationProviders(configurationProvider);
+
+ // The test configuration in "struts-testing.xml" should define a "default" package. That "default" package should contain a "defaultInterceptorStack" containing
+ // a "fetchMetadata" interceptor parameter "fetchMetadata.setExemptedPaths". If the parameter method injection is working correctly for the FetchMetadataInterceptor,
+ // the exempted paths should be set appropriately for the interceptor instances, once the configuration is loaded into the container.
+ final PackageConfig defaultPackageConfig = configuration.getPackageConfig("default");
+ final InterceptorStackConfig defaultInterceptorStackConfig = (InterceptorStackConfig) defaultPackageConfig.getInterceptorConfig("defaultInterceptorStack");
+ final Collection<InterceptorMapping> defaultInterceptorStackInterceptors = defaultInterceptorStackConfig.getInterceptors();
+ assertFalse("'defaultInterceptorStack' interceptors in struts-testing.xml is empty ?", defaultInterceptorStackInterceptors.isEmpty());
+ InterceptorMapping configuredFetchMetadataInterceptorMapping = null;
+ Iterator<InterceptorMapping> interceptorIterator = defaultInterceptorStackInterceptors.iterator();
+ while (interceptorIterator.hasNext()) {
+ InterceptorMapping currentMapping = interceptorIterator.next();
+ if (currentMapping != null && "fetchMetadata".equals(currentMapping.getName())) {
+ configuredFetchMetadataInterceptorMapping = currentMapping;
+ break;
+ }
+ }
+ assertNotNull("'fetchMetadata' interceptor mapping not present after loading 'struts-testing.xml' ?", configuredFetchMetadataInterceptorMapping);
+ assertTrue("'fetchMetadata' interceptor mapping loaded from 'struts-testing.xml' produced a non-FetchMetadataInterceptor type ?", configuredFetchMetadataInterceptorMapping.getInterceptor() instanceof FetchMetadataInterceptor);
+ FetchMetadataInterceptor configuredFetchMetadataInterceptor = (FetchMetadataInterceptor) configuredFetchMetadataInterceptorMapping.getInterceptor();
+ request.removeHeader(SEC_FETCH_SITE_HEADER);
+ request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
+ request.setContextPath("/foo");
+ assertEquals("Expected interceptor to NOT accept this request [/foo]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+ request.setContextPath("/fetchMetadataExemptedGlobal");
+ assertNotEquals("Expected interceptor to accept this request [/fetchMetadataExemptedGlobal]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+ request.setContextPath("/someOtherPath");
+ assertNotEquals("Expected interceptor to accept this request [/someOtherPath]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+
+ // The test configuration in "struts-testing.xml" should also contain three actions configured differently for the "fetchMetadata" interceptor.
+ // "fetchMetadataExempted" has an override exemption matching its action name, "fetchMetadataNotExempted" has an override exemption NOT matching its action name,
+ // and "fetchMetadataExemptedGlobal" has an action name matching an exemption defined in "defaultInterceptorStack".
+ final RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration();
+ final ActionConfig fetchMetadataExemptedActionConfig = runtimeConfiguration.getActionConfig("/", "fetchMetadataExempted");
+ final ActionConfig fetchMetadataNotExemptedActionConfig = runtimeConfiguration.getActionConfig("/", "fetchMetadataNotExempted");
+ final ActionConfig fetchMetadataExemptedGlobalActionConfig = runtimeConfiguration.getActionConfig("/", "fetchMetadataExemptedGlobal");
+ assertNotNull("'fetchMetadataExempted' action config not present in 'struts-testing.xml' ?", fetchMetadataExemptedActionConfig);
+ assertNotNull("'fetchMetadataNotExempted' action config not present in 'struts-testing.xml' ?", fetchMetadataExemptedActionConfig);
+ assertNotNull("'fetchMetadataExemptedGlobal' action config not present in 'struts-testing.xml' ?", fetchMetadataExemptedActionConfig);
+
+ // Test fetchMetadata interceptor for the "fetchMetadataExempted" action.
+ Collection<InterceptorMapping> currentActionInterceptors = fetchMetadataExemptedActionConfig.getInterceptors();
+ assertFalse("'fetchMetadataExempted' interceptors in struts-testing.xml is empty ?", currentActionInterceptors.isEmpty());
+ configuredFetchMetadataInterceptorMapping = null;
+ interceptorIterator = currentActionInterceptors.iterator();
+ while (interceptorIterator.hasNext()) {
+ InterceptorMapping currentMapping = interceptorIterator.next();
+ if (currentMapping != null && "fetchMetadata".equals(currentMapping.getName())) {
+ configuredFetchMetadataInterceptorMapping = currentMapping;
+ break;
+ }
+ }
+ assertNotNull("'fetchMetadata' interceptor mapping for action 'fetchMetadataExempted' not present in 'struts-testing.xml' ?", configuredFetchMetadataInterceptorMapping);
+ assertTrue("'fetchMetadata' interceptor mapping for action 'fetchMetadataExempted' in 'struts-testing.xml' produced a non-FetchMetadataInterceptor type ?", configuredFetchMetadataInterceptorMapping.getInterceptor() instanceof FetchMetadataInterceptor);
+ configuredFetchMetadataInterceptor = (FetchMetadataInterceptor) configuredFetchMetadataInterceptorMapping.getInterceptor();
+ request.removeHeader(SEC_FETCH_SITE_HEADER);
+ request.addHeader(SEC_FETCH_SITE_HEADER, fetchMetadataExemptedActionConfig.getName());
+ request.setContextPath("/" + fetchMetadataExemptedActionConfig.getName());
+ assertNotEquals("Expected interceptor to accept this request [" + "/" + fetchMetadataExemptedActionConfig.getName() + "]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+
+ // Test fetchMetadata interceptor for the "fetchMetadataNotExempted" action.
+ currentActionInterceptors = fetchMetadataNotExemptedActionConfig.getInterceptors();
+ assertFalse("'fetchMetadataNotExempted' interceptors in struts-testing.xml is empty ?", currentActionInterceptors.isEmpty());
+ configuredFetchMetadataInterceptorMapping = null;
+ interceptorIterator = currentActionInterceptors.iterator();
+ while (interceptorIterator.hasNext()) {
+ InterceptorMapping currentMapping = interceptorIterator.next();
+ if (currentMapping != null && "fetchMetadata".equals(currentMapping.getName())) {
+ configuredFetchMetadataInterceptorMapping = currentMapping;
+ break;
+ }
+ }
+ assertNotNull("'fetchMetadata' interceptor mapping for action 'fetchMetadataNotExempted' not present in 'struts-testing.xml' ?", configuredFetchMetadataInterceptorMapping);
+ assertTrue("'fetchMetadata' interceptor mapping 'fetchMetadataExempted' in 'struts-testing.xml' produced a non-FetchMetadataInterceptor type ?", configuredFetchMetadataInterceptorMapping.getInterceptor() instanceof FetchMetadataInterceptor);
+ configuredFetchMetadataInterceptor = (FetchMetadataInterceptor) configuredFetchMetadataInterceptorMapping.getInterceptor();
+ request.removeHeader(SEC_FETCH_SITE_HEADER);
+ request.addHeader(SEC_FETCH_SITE_HEADER, fetchMetadataNotExemptedActionConfig.getName());
+ request.setContextPath("/" + fetchMetadataNotExemptedActionConfig.getName());
+ assertEquals("Expected interceptor to NOT accept this request [" + "/" + fetchMetadataNotExemptedActionConfig.getName() + "]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+
+ // Test fetchMetadata interceptor for the "fetchMetadataExemptedGlobal" action.
+ currentActionInterceptors = fetchMetadataExemptedGlobalActionConfig.getInterceptors();
+ assertFalse("'fetchMetadataExemptedGlobal' interceptors in struts-testing.xml is empty ?", currentActionInterceptors.isEmpty());
+ configuredFetchMetadataInterceptorMapping = null;
+ interceptorIterator = currentActionInterceptors.iterator();
+ while (interceptorIterator.hasNext()) {
+ InterceptorMapping currentMapping = interceptorIterator.next();
+ if (currentMapping != null && "fetchMetadata".equals(currentMapping.getName())) {
+ configuredFetchMetadataInterceptorMapping = currentMapping;
+ break;
+ }
+ }
+ assertNotNull("'fetchMetadata' interceptor mapping for action 'fetchMetadataExemptedGlobal' not present in 'struts-testing.xml' ?", configuredFetchMetadataInterceptorMapping);
+ assertTrue("'fetchMetadata' interceptor mapping 'fetchMetadataExemptedGlobal' in 'struts-testing.xml' produced a non-FetchMetadataInterceptor type ?", configuredFetchMetadataInterceptorMapping.getInterceptor() instanceof FetchMetadataInterceptor);
+ configuredFetchMetadataInterceptor = (FetchMetadataInterceptor) configuredFetchMetadataInterceptorMapping.getInterceptor();
+ request.removeHeader(SEC_FETCH_SITE_HEADER);
+ request.addHeader(SEC_FETCH_SITE_HEADER, fetchMetadataExemptedGlobalActionConfig.getName());
+ request.setContextPath("/" + fetchMetadataExemptedGlobalActionConfig.getName());
+ assertNotEquals("Expected interceptor to accept this request [" + "/" + fetchMetadataExemptedGlobalActionConfig.getName() + "]", SC_FORBIDDEN, configuredFetchMetadataInterceptor.intercept(mai));
+ }
+
}
diff --git a/core/src/test/resources/struts-testing.xml b/core/src/test/resources/struts-testing.xml
new file mode 100644
index 0000000..5d9c5b2
--- /dev/null
+++ b/core/src/test/resources/struts-testing.xml
@@ -0,0 +1,55 @@
+<?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 2.5//EN"
+ "http://struts.apache.org/dtds/struts-2.5.dtd">
+<struts>
+ <include file="struts-default.xml"/>
+ <package name="default" extends="struts-default" namespace="/">
+ <interceptors>
+ <interceptor-stack name="defaultInterceptorStack">
+ <interceptor-ref name="defaultStack">
+ <param name="fetchMetadata.setExemptedPaths">/fetchMetadataExemptedGlobal,/someOtherPath</param>
+ </interceptor-ref>
+ </interceptor-stack>
+ </interceptors>
+
+ <action name="fetchMetadataExempted" class="com.opensymphony.xwork2.SimpleAction">
+ <interceptor-ref name="defaultInterceptorStack">
+ <param name="fetchMetadata.setExemptedPaths">/fetchMetadataExempted</param>
+ </interceptor-ref>
+ <result name="success">hello.jsp</result>
+ </action>
+
+ <action name="fetchMetadataNotExempted" class="com.opensymphony.xwork2.SimpleAction">
+ <interceptor-ref name="defaultInterceptorStack">
+ <param name="fetchMetadata.setExemptedPaths">/nonMatchingActionPath</param>
+ </interceptor-ref>
+ <result name="success">hello.jsp</result>
+ </action>
+
+ <action name="fetchMetadataExemptedGlobal" class="com.opensymphony.xwork2.SimpleAction">
+ <interceptor-ref name="defaultInterceptorStack" />
+ <result name="success">hello.jsp</result>
+ </action>
+ </package>
+</struts>