mirror of
https://github.com/apache/struts.git
synced 2026-08-11 01:27:14 +00:00
WW-5275 Allows to provide a custom CspSettings per action
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.action;
|
||||
|
||||
import org.apache.struts2.interceptor.csp.CspSettings;
|
||||
|
||||
/**
|
||||
* Implement this interface by an action to provide a custom {@link CspSettings},
|
||||
* see {@link org.apache.struts2.interceptor.csp.CspInterceptor} for more details
|
||||
*
|
||||
* @since Struts 6.2.0
|
||||
*/
|
||||
public interface CspSettingsAware {
|
||||
|
||||
CspSettings getCspSettings();
|
||||
|
||||
}
|
||||
@@ -20,7 +20,9 @@ package org.apache.struts2.interceptor.csp;
|
||||
|
||||
import com.opensymphony.xwork2.ActionInvocation;
|
||||
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
|
||||
import com.opensymphony.xwork2.interceptor.PreResultListener;
|
||||
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;
|
||||
@@ -37,20 +39,43 @@ import java.util.Optional;
|
||||
* @see CspSettings
|
||||
* @see DefaultCspSettings
|
||||
**/
|
||||
public final class CspInterceptor extends AbstractInterceptor implements PreResultListener {
|
||||
public final class CspInterceptor extends AbstractInterceptor {
|
||||
|
||||
private final CspSettings settings = new DefaultCspSettings();
|
||||
private static final Logger LOG = LogManager.getLogger(CspInterceptor.class);
|
||||
|
||||
private Boolean enforcingMode;
|
||||
private String reportUri;
|
||||
|
||||
@Override
|
||||
public String intercept(ActionInvocation invocation) throws Exception {
|
||||
invocation.addPreResultListener(this);
|
||||
Object action = invocation.getAction();
|
||||
if (action instanceof CspSettingsAware) {
|
||||
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());
|
||||
}
|
||||
return invocation.invoke();
|
||||
}
|
||||
|
||||
public void beforeResult(ActionInvocation invocation, String resultCode) {
|
||||
private void applySettings(ActionInvocation invocation, CspSettings cspSettings) {
|
||||
if (enforcingMode != null) {
|
||||
LOG.trace("Applying: {} to enforcingMode", enforcingMode);
|
||||
cspSettings.setEnforcingMode(enforcingMode);
|
||||
}
|
||||
if (reportUri != null) {
|
||||
LOG.trace("Applying: {} to reportUri", reportUri);
|
||||
cspSettings.setReportUri(reportUri);
|
||||
}
|
||||
|
||||
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
|
||||
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
|
||||
settings.addCspHeaders(request, response);
|
||||
|
||||
invocation.addPreResultListener((actionInvocation, resultCode) -> {
|
||||
LOG.trace("Applying CSP header: {} to the request", cspSettings);
|
||||
cspSettings.addCspHeaders(request, response);
|
||||
});
|
||||
}
|
||||
|
||||
public void setReportUri(String reportUri) {
|
||||
@@ -63,21 +88,19 @@ public final class CspInterceptor extends AbstractInterceptor implements PreResu
|
||||
throw new IllegalArgumentException("Illegal configuration: report URI is not relative to the root. Please set a report URI that starts with /");
|
||||
}
|
||||
|
||||
settings.setReportUri(reportUri);
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
private Optional<URI> buildUri(String reportUri) {
|
||||
try {
|
||||
return Optional.of(URI.create(reportUri));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void setEnforcingMode(String value) {
|
||||
boolean enforcingMode = Boolean.parseBoolean(value);
|
||||
settings.setEnforcingMode(enforcingMode);
|
||||
this.enforcingMode = Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,9 +51,13 @@ public interface CspSettings {
|
||||
|
||||
void addCspHeaders(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
// sets the uri where csp violation reports will be sent
|
||||
/**
|
||||
* Sets the uri where csp violation reports will be sent
|
||||
*/
|
||||
void setReportUri(String uri);
|
||||
|
||||
// sets CSP headers in enforcing mode when true, and report-only when false
|
||||
/**
|
||||
* Sets CSP headers in enforcing mode when true, and report-only when false
|
||||
*/
|
||||
void setEnforcingMode(boolean value);
|
||||
}
|
||||
|
||||
@@ -111,4 +111,12 @@ public class DefaultCspSettings implements CspSettings {
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultCspSettings{" +
|
||||
"reportUri='" + reportUri + '\'' +
|
||||
", cspHeader='" + cspHeader + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,23 +22,16 @@ import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.action.CspSettingsAware;
|
||||
import org.apache.struts2.dispatcher.SessionMap;
|
||||
import org.apache.struts2.interceptor.csp.CspInterceptor;
|
||||
import org.apache.struts2.interceptor.csp.CspSettings;
|
||||
import org.apache.struts2.interceptor.csp.DefaultCspSettings;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.BASE_URI;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.CSP_ENFORCE_HEADER;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.CSP_REPORT_HEADER;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.HTTP;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.HTTPS;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.NONE;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.OBJECT_SRC;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.REPORT_URI;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.SCRIPT_SRC;
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.STRICT_DYNAMIC;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
@@ -145,28 +138,35 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testCustomPreResultListener() throws Exception {
|
||||
mai.setAction(new CustomerCspAction("/report-uri"));
|
||||
interceptor.setEnforcingMode("false");
|
||||
interceptor.intercept(mai);
|
||||
checkHeader("/report-uri", "false");
|
||||
}
|
||||
|
||||
public void checkHeader(String reportUri, String enforcingMode) {
|
||||
String expectedCspHeader;
|
||||
if (Strings.isEmpty(reportUri)) {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; ",
|
||||
OBJECT_SRC, NONE,
|
||||
SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, 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",
|
||||
OBJECT_SRC, NONE,
|
||||
SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, NONE,
|
||||
REPORT_URI, reportUri
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
String header;
|
||||
if (enforcingMode.equals("true")) {
|
||||
header = response.getHeader(CSP_ENFORCE_HEADER);
|
||||
header = response.getHeader(CspSettings.CSP_ENFORCE_HEADER);
|
||||
} else {
|
||||
header = response.getHeader(CSP_REPORT_HEADER);
|
||||
header = response.getHeader(CspSettings.CSP_REPORT_HEADER);
|
||||
}
|
||||
|
||||
assertFalse("No CSP header exists", Strings.isEmpty(header));
|
||||
@@ -185,4 +185,20 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
mai.setInvocationContext(context);
|
||||
session = request.getSession();
|
||||
}
|
||||
|
||||
private static class CustomerCspAction implements CspSettingsAware {
|
||||
|
||||
private final String reportUri;
|
||||
|
||||
private CustomerCspAction(String reportUri) {
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CspSettings getCspSettings() {
|
||||
DefaultCspSettings settings = new DefaultCspSettings();
|
||||
settings.setReportUri(reportUri);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user