blob: 7d7c4bd2108ca07de5a61b1058de280c8bd49634 [file] [log] [blame]
/*
* 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.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.apache.struts2.config.StrutsXmlConfigurationProvider;
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 {
private final FetchMetadataInterceptor interceptor = new FetchMetadataInterceptor();
private final MockActionInvocation mai = new MockActionInvocation();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
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 {
super.setUp();
container.inject(interceptor);
interceptor.setExemptedPaths("/foo,/bar");
ServletActionContext.setRequest(request);
ServletActionContext.setResponse(response);
ActionContext context = ServletActionContext.getActionContext();
mai.setInvocationContext(context);
}
public void testNoSite() throws Exception {
request.removeHeader(SEC_FETCH_SITE_HEADER);
assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testValidSite() throws Exception {
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", SC_FORBIDDEN, interceptor.intercept(mai));
}
}
public void testValidTopLevelNavigation() throws Exception {
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", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testInvalidTopLevelNavigation() throws Exception {
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", SC_FORBIDDEN, interceptor.intercept(mai));
}
}
public void testPathInExemptedPaths() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/foo");
assertNotEquals("Expected interceptor to accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testPathNotInExemptedPaths() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/foobar");
assertEquals("Expected interceptor to NOT accept this request", SC_FORBIDDEN, interceptor.intercept(mai));
}
public void testVaryHeaderAcceptedReq() throws Exception {
request.addHeader(SEC_FETCH_SITE_HEADER, "foo");
request.setContextPath("/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 testVaryHeaderRejectedReq() throws Exception {
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 StrutsXmlConfigurationProvider("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));
}
}