mirror of
https://github.com/apache/struts.git
synced 2026-08-06 15:17:00 +00:00
Merge pull request #590 from apache/WW-5215-csp-session
[WW-5215] Checks if session was already created before applying CSP settings
This commit is contained in:
@@ -23,6 +23,7 @@ import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
|
||||
import com.opensymphony.xwork2.interceptor.PreResultListener;
|
||||
import java.net.URI;
|
||||
import java.util.Optional;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
* @see DefaultCspSettings
|
||||
**/
|
||||
public final class CspInterceptor extends AbstractInterceptor implements PreResultListener {
|
||||
|
||||
private final CspSettings settings = new DefaultCspSettings();
|
||||
|
||||
@Override
|
||||
@@ -45,8 +47,9 @@ public final class CspInterceptor extends AbstractInterceptor implements PreResu
|
||||
}
|
||||
|
||||
public void beforeResult(ActionInvocation invocation, String resultCode) {
|
||||
HttpServletRequest request = invocation.getInvocationContext().getServletRequest();
|
||||
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
|
||||
settings.addCspHeaders(response);
|
||||
settings.addCspHeaders(request, response);
|
||||
}
|
||||
|
||||
public void setReportUri(String reportUri) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.struts2.interceptor.csp;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
@@ -42,9 +43,17 @@ public interface CspSettings {
|
||||
String HTTPS = "https:";
|
||||
String CSP_REPORT_TYPE = "application/csp-report";
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #addCspHeaders(HttpServletRequest, HttpServletResponse)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void addCspHeaders(HttpServletResponse response);
|
||||
|
||||
void addCspHeaders(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
// 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
|
||||
void setEnforcingMode(boolean value);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@
|
||||
*/
|
||||
package org.apache.struts2.interceptor.csp;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@@ -37,50 +38,61 @@ import static java.lang.String.format;
|
||||
*/
|
||||
public class DefaultCspSettings implements CspSettings {
|
||||
|
||||
private final static Logger LOG = LogManager.getLogger(DefaultCspSettings.class);
|
||||
|
||||
private final SecureRandom sRand = new SecureRandom();
|
||||
|
||||
// this supplier computes a policy format
|
||||
private final Supplier<String> lazyPolicyBuilder = new Supplier<String>() {
|
||||
@Override
|
||||
public String get() {
|
||||
StringBuilder policyFormatBuilder = new StringBuilder()
|
||||
.append(OBJECT_SRC)
|
||||
.append(format(" '%s'; ", NONE))
|
||||
.append(SCRIPT_SRC)
|
||||
.append(" 'nonce-%s' ") // nonce placeholder
|
||||
.append(format("'%s' ", STRICT_DYNAMIC))
|
||||
.append(format("%s %s; ", HTTP, HTTPS))
|
||||
.append(BASE_URI)
|
||||
.append(format(" '%s'; ", NONE));
|
||||
|
||||
if (reportUri != null) {
|
||||
policyFormatBuilder
|
||||
.append(REPORT_URI)
|
||||
.append(format(" %s", reportUri));
|
||||
}
|
||||
|
||||
return format(policyFormatBuilder.toString(), getNonceString());
|
||||
}
|
||||
};
|
||||
|
||||
private String reportUri;
|
||||
// default to reporting mode
|
||||
private String cspHeader = CSP_REPORT_HEADER;
|
||||
|
||||
@Override
|
||||
public void addCspHeaders(HttpServletResponse response) {
|
||||
associateNonceWithSession();
|
||||
response.setHeader(cspHeader, lazyPolicyBuilder.get());
|
||||
throw new UnsupportedOperationException("Unsupported implementation, use #addCspHeaders(HttpServletRequest request, HttpServletResponse response)");
|
||||
}
|
||||
|
||||
private String getNonceString() {
|
||||
Map<String, Object> session = ActionContext.getContext().getSession();
|
||||
return (String) session.get("nonce");
|
||||
public void addCspHeaders(HttpServletRequest request, HttpServletResponse response) {
|
||||
if (isSessionActive(request)) {
|
||||
LOG.debug("Session is active, applying CSP settings");
|
||||
associateNonceWithSession(request);
|
||||
response.setHeader(cspHeader, cratePolicyFormat(request));
|
||||
} else {
|
||||
LOG.debug("Session is not active, ignoring CSP settings");
|
||||
}
|
||||
}
|
||||
|
||||
private void associateNonceWithSession() {
|
||||
Map<String, Object> session = ActionContext.getContext().getSession();
|
||||
private boolean isSessionActive(HttpServletRequest request) {
|
||||
return request.getSession(false) != null;
|
||||
}
|
||||
|
||||
private void associateNonceWithSession(HttpServletRequest request) {
|
||||
String nonceValue = Base64.getUrlEncoder().encodeToString(getRandomBytes());
|
||||
session.put("nonce", nonceValue);
|
||||
request.getSession().setAttribute("nonce", nonceValue);
|
||||
}
|
||||
|
||||
private String cratePolicyFormat(HttpServletRequest request) {
|
||||
StringBuilder policyFormatBuilder = new StringBuilder()
|
||||
.append(OBJECT_SRC)
|
||||
.append(format(" '%s'; ", NONE))
|
||||
.append(SCRIPT_SRC)
|
||||
.append(" 'nonce-%s' ") // nonce placeholder
|
||||
.append(format("'%s' ", STRICT_DYNAMIC))
|
||||
.append(format("%s %s; ", HTTP, HTTPS))
|
||||
.append(BASE_URI)
|
||||
.append(format(" '%s'; ", NONE));
|
||||
|
||||
if (reportUri != null) {
|
||||
policyFormatBuilder
|
||||
.append(REPORT_URI)
|
||||
.append(format(" %s", reportUri));
|
||||
}
|
||||
|
||||
return format(policyFormatBuilder.toString(), getNonceString(request));
|
||||
}
|
||||
|
||||
private String getNonceString(HttpServletRequest request) {
|
||||
Object nonce = request.getSession().getAttribute("nonce");
|
||||
return Objects.toString(nonce);
|
||||
}
|
||||
|
||||
private byte[] getRandomBytes() {
|
||||
@@ -98,4 +110,5 @@ public class DefaultCspSettings implements CspSettings {
|
||||
public void setReportUri(String reportUri) {
|
||||
this.reportUri = reportUri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,16 +21,25 @@ package org.apache.struts2.interceptor;
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.StrutsInternalTestCase;
|
||||
import org.apache.struts2.dispatcher.SessionMap;
|
||||
import org.apache.struts2.interceptor.csp.CspInterceptor;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import static org.apache.struts2.interceptor.csp.CspSettings.*;
|
||||
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 {
|
||||
|
||||
@@ -38,7 +47,8 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
private final MockActionInvocation mai = new MockActionInvocation();
|
||||
private final MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
private final MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
private final Map<String, Object> session = new HashMap<>();
|
||||
|
||||
private HttpSession session;
|
||||
|
||||
public void test_whenRequestReceived_thenNonceIsSetInSession_andCspHeaderContainsIt() throws Exception {
|
||||
String reportUri = "/barfoo";
|
||||
@@ -48,8 +58,8 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertTrue("Nonce key does not exist", session.containsKey("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.get("nonce")));
|
||||
assertNotNull("Nonce key does not exist", session.getAttribute("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.getAttribute("nonce")));
|
||||
checkHeader(reportUri, reporting);
|
||||
}
|
||||
|
||||
@@ -58,13 +68,13 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
String enforcingMode = "true";
|
||||
interceptor.setReportUri(reportUri);
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
session.put("nonce", "foo");
|
||||
session.setAttribute("nonce", "foo");
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertTrue("Nonce key does not exist", session.containsKey("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.get("nonce")));
|
||||
assertFalse("New nonce value couldn't be set", session.get("nonce").equals("foo"));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -73,13 +83,13 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
String enforcingMode = "true";
|
||||
interceptor.setReportUri(reportUri);
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
session.put("nonce", "foo");
|
||||
session.setAttribute("nonce", "foo");
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertTrue("Nonce key does not exist", session.containsKey("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.get("nonce")));
|
||||
assertFalse("New nonce value couldn't be set", session.get("nonce").equals("foo"));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -88,13 +98,12 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
String enforcingMode = "false";
|
||||
interceptor.setReportUri(reportUri);
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
session.put("nonce", "foo");
|
||||
session.setAttribute("nonce", "foo");
|
||||
|
||||
interceptor.intercept(mai);
|
||||
|
||||
assertTrue("Nonce key does not exist", session.containsKey("nonce"));
|
||||
assertFalse("Nonce value is empty", Strings.isEmpty((String) session.get("nonce")));
|
||||
assertFalse("New nonce value couldn't be set", session.get("nonce").equals("foo"));
|
||||
assertNotNull("Nonce value is empty", session.getAttribute("nonce"));
|
||||
assertNotEquals("New nonce value couldn't be set", "foo", session.getAttribute("nonce"));
|
||||
checkHeader(reportUri, enforcingMode);
|
||||
}
|
||||
|
||||
@@ -116,11 +125,11 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
String enforcingMode = "false";
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
|
||||
try{
|
||||
try {
|
||||
interceptor.setReportUri("ww w. google.@com");
|
||||
assert(false);
|
||||
} catch (IllegalArgumentException e){
|
||||
assert(true);
|
||||
assert (false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
assert (true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,33 +137,33 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
String enforcingMode = "false";
|
||||
interceptor.setEnforcingMode(enforcingMode);
|
||||
|
||||
try{
|
||||
try {
|
||||
interceptor.setReportUri("some-uri");
|
||||
assert(false);
|
||||
} catch (IllegalArgumentException e){
|
||||
assert(true);
|
||||
assert (false);
|
||||
} catch (IllegalArgumentException e) {
|
||||
assert (true);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkHeader(String reportUri, String enforcingMode){
|
||||
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.get("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, NONE
|
||||
OBJECT_SRC, NONE,
|
||||
SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, NONE
|
||||
);
|
||||
} else {
|
||||
expectedCspHeader = String.format("%s '%s'; %s 'nonce-%s' '%s' %s %s; %s '%s'; %s %s",
|
||||
OBJECT_SRC, NONE,
|
||||
SCRIPT_SRC, session.get("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, NONE,
|
||||
REPORT_URI, reportUri
|
||||
OBJECT_SRC, NONE,
|
||||
SCRIPT_SRC, session.getAttribute("nonce"), STRICT_DYNAMIC, HTTP, HTTPS,
|
||||
BASE_URI, NONE,
|
||||
REPORT_URI, reportUri
|
||||
);
|
||||
}
|
||||
|
||||
String header = "";
|
||||
if (enforcingMode.equals("true")){
|
||||
if (enforcingMode.equals("true")) {
|
||||
header = response.getHeader(CSP_ENFORCE_HEADER);
|
||||
} else {
|
||||
header = response.getHeader(CSP_REPORT_HEADER);
|
||||
@@ -168,10 +177,12 @@ public class CspInterceptorTest extends StrutsInternalTestCase {
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
container.inject(interceptor);
|
||||
ServletActionContext.setRequest(request);
|
||||
ServletActionContext.setResponse(response);
|
||||
ActionContext context = ServletActionContext.getActionContext().bind();
|
||||
context.withSession(session);
|
||||
ActionContext context = ActionContext.getContext()
|
||||
.withServletRequest(request)
|
||||
.withServletResponse(response)
|
||||
.withSession(new SessionMap<>(request))
|
||||
.bind();
|
||||
mai.setInvocationContext(context);
|
||||
session = request.getSession();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,8 @@ public class FreemarkerResultMockedTest extends StrutsInternalTestCase {
|
||||
EasyMock.replay(servletContext);
|
||||
|
||||
init();
|
||||
// create session
|
||||
request.getSession();
|
||||
|
||||
request.setRequestURI("/tutorial/test10.action");
|
||||
ActionMapping mapping = container.getInstance(ActionMapper.class).getMapping(request, configurationManager);
|
||||
|
||||
Reference in New Issue
Block a user