Skip to content

Commit 471e4e7

Browse files
committed
WW-5504 Allows to use request instead of session attribute to store nonce
1 parent c7a6daf commit 471e4e7

32 files changed

+379
-40
lines changed

core/src/main/java/com/opensymphony/xwork2/config/providers/StrutsDefaultConfigurationProvider.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
import ognl.PropertyAccessor;
6969
import org.apache.struts2.dispatcher.HttpParameters;
7070
import org.apache.struts2.dispatcher.Parameter;
71+
import org.apache.struts2.interceptor.csp.CspNonceReader;
72+
import org.apache.struts2.interceptor.csp.StrutsCspNonceReader;
7173
import org.apache.struts2.interceptor.exec.ExecutorProvider;
7274
import org.apache.struts2.interceptor.exec.StrutsExecutorProvider;
7375
import org.apache.struts2.url.QueryStringBuilder;
@@ -159,7 +161,9 @@ public void register(ContainerBuilder builder, LocatableProperties props) throws
159161
.factory(UrlEncoder.class, StrutsUrlEncoder.class, Scope.SINGLETON)
160162
.factory(UrlDecoder.class, StrutsUrlDecoder.class, Scope.SINGLETON)
161163

162-
.factory(ExecutorProvider.class, StrutsExecutorProvider.class, Scope.SINGLETON);
164+
.factory(ExecutorProvider.class, StrutsExecutorProvider.class, Scope.SINGLETON)
165+
166+
.factory(CspNonceReader.class, StrutsCspNonceReader.class, Scope.SINGLETON);
163167

164168
for (Map.Entry<String, Object> entry : DefaultConfiguration.BOOTSTRAP_CONSTANTS.entrySet()) {
165169
props.setProperty(entry.getKey(), String.valueOf(entry.getValue()));

core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -419,8 +419,12 @@ Object construct(InternalContext context, Class<? super T> expectedType) {
419419
// First time through...
420420
constructionContext.startConstruction();
421421
try {
422-
final Object[] parameters = getParameters(constructor, context, parameterInjectors);
423-
t = constructor.newInstance(parameters);
422+
if (constructor.getParameterCount() > 0 && parameterInjectors == null) {
423+
t = constructor.newInstance(new Object[constructor.getParameterCount()]);
424+
} else {
425+
final Object[] parameters = getParameters(constructor, context, parameterInjectors);
426+
t = constructor.newInstance(parameters);
427+
}
424428
constructionContext.setProxyDelegates(t);
425429
} finally {
426430
constructionContext.finishConstruction();

core/src/main/java/org/apache/struts2/StrutsConstants.java

+7
Original file line numberDiff line numberDiff line change
@@ -509,4 +509,11 @@ public final class StrutsConstants {
509509

510510
/** See {@link org.apache.struts2.interceptor.exec.ExecutorProvider} */
511511
public static final String STRUTS_EXECUTOR_PROVIDER = "struts.executor.provider";
512+
513+
/**
514+
* See {@link org.apache.struts2.interceptor.csp.CspNonceReader}
515+
* @since 6.8.0
516+
*/
517+
public static final String STRUTS_CSP_NONCE_READER = "struts.csp.nonce.reader";
518+
public static final String STRUTS_CSP_NONCE_SOURCE = "struts.csp.nonce.source";
512519
}

core/src/main/java/org/apache/struts2/components/UIBean.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@
3333
import org.apache.struts2.components.template.TemplateEngineManager;
3434
import org.apache.struts2.components.template.TemplateRenderingContext;
3535
import org.apache.struts2.dispatcher.StaticContentLoader;
36+
import org.apache.struts2.interceptor.csp.CspNonceReader;
3637
import org.apache.struts2.util.ComponentUtils;
3738
import org.apache.struts2.util.TextProviderHelper;
3839
import org.apache.struts2.views.annotations.StrutsTagAttribute;
3940
import org.apache.struts2.views.util.ContextUtil;
4041

4142
import javax.servlet.http.HttpServletRequest;
4243
import javax.servlet.http.HttpServletResponse;
43-
import javax.servlet.http.HttpSession;
4444
import java.io.Writer;
4545
import java.util.HashMap;
4646
import java.util.LinkedHashMap;
@@ -521,6 +521,8 @@ public UIBean(ValueStack stack, HttpServletRequest request, HttpServletResponse
521521

522522
protected TemplateEngineManager templateEngineManager;
523523

524+
protected CspNonceReader cspNonceReader;
525+
524526
@Inject(StrutsConstants.STRUTS_UI_TEMPLATEDIR)
525527
public void setDefaultTemplateDir(String dir) {
526528
this.defaultTemplateDir = dir;
@@ -546,6 +548,11 @@ public void setTemplateEngineManager(TemplateEngineManager mgr) {
546548
this.templateEngineManager = mgr;
547549
}
548550

551+
@Inject
552+
public void setCspNonceReader(CspNonceReader cspNonceReader) {
553+
this.cspNonceReader = cspNonceReader;
554+
}
555+
549556
@Override
550557
public boolean end(Writer writer, String body) {
551558
evaluateParams();
@@ -864,13 +871,12 @@ public void evaluateParams() {
864871
}
865872

866873
// to be used with the CSP interceptor - adds the nonce value as a parameter to be accessed from ftl files
867-
HttpSession session = stack.getActionContext().getServletRequest().getSession(false);
868-
Object nonceValue = session != null ? session.getAttribute("nonce") : null;
874+
CspNonceReader.NonceValue nonceValue = cspNonceReader.readNonceValue(stack);
869875

870-
if (nonceValue != null) {
871-
addParameter("nonce", nonceValue.toString());
876+
if (nonceValue.isNonceValueSet()) {
877+
addParameter("nonce", nonceValue.getNonceValue());
872878
} else {
873-
LOG.debug("Session is not active, cannot obtain nonce value");
879+
LOG.debug("Nonce not defined in: {}", nonceValue.getSource());
874880
}
875881

876882
evaluateExtraParams();

core/src/main/java/org/apache/struts2/config/StrutsBeanSelectionProvider.java

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import org.apache.struts2.dispatcher.StaticContentLoader;
7070
import org.apache.struts2.dispatcher.mapper.ActionMapper;
7171
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
72+
import org.apache.struts2.interceptor.csp.CspNonceReader;
7273
import org.apache.struts2.interceptor.exec.ExecutorProvider;
7374
import org.apache.struts2.ognl.OgnlGuard;
7475
import org.apache.struts2.url.QueryStringBuilder;
@@ -450,6 +451,8 @@ public void register(ContainerBuilder builder, LocatableProperties props) {
450451

451452
alias(ExecutorProvider.class, StrutsConstants.STRUTS_EXECUTOR_PROVIDER, builder, props, Scope.SINGLETON);
452453

454+
alias(CspNonceReader.class, StrutsConstants.STRUTS_CSP_NONCE_READER, builder, props, Scope.SINGLETON);
455+
453456
switchDevMode(props);
454457
}
455458

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.struts2.interceptor.csp;
20+
21+
import org.apache.struts2.util.ValueStack;
22+
23+
/**
24+
* Reads the nonce value using the ValueStack, {@link StrutsCspNonceReader} is the default implementation
25+
* @since 6.8.0
26+
*/
27+
public interface CspNonceReader {
28+
29+
NonceValue readNonceValue(ValueStack stack);
30+
31+
class NonceValue {
32+
private final String nonceValue;
33+
private final CspNonceSource source;
34+
35+
private NonceValue(String nonceValue, CspNonceSource source) {
36+
this.nonceValue = nonceValue;
37+
this.source = source;
38+
}
39+
40+
public static NonceValue ofSession(String nonceValue) {
41+
return new NonceValue(nonceValue, CspNonceSource.SESSION);
42+
}
43+
44+
public static NonceValue ofRequest(String nonceValue) {
45+
return new NonceValue(nonceValue, CspNonceSource.REQUEST);
46+
}
47+
48+
public static NonceValue ofNullSession() {
49+
return new NonceValue(null, CspNonceSource.REQUEST);
50+
}
51+
52+
public static NonceValue ofNullRequest() {
53+
return new NonceValue(null, CspNonceSource.REQUEST);
54+
}
55+
56+
public boolean isNonceValueSet() {
57+
return nonceValue != null;
58+
}
59+
60+
public String getNonceValue() {
61+
return nonceValue;
62+
}
63+
64+
public CspNonceSource getSource() {
65+
return source;
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return "NonceValue{" +
71+
String.format("nonceValue='%s**********'", nonceValue.substring(0, 4)) +
72+
", source=" + source +
73+
'}';
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.struts2.interceptor.csp;
20+
21+
/**
22+
* Source of the nonce value
23+
*/
24+
public enum CspNonceSource {
25+
REQUEST,
26+
SESSION
27+
}

core/src/main/java/org/apache/struts2/interceptor/csp/DefaultCspSettings.java

+51-22
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
*/
1919
package org.apache.struts2.interceptor.csp;
2020

21+
import com.opensymphony.xwork2.inject.Inject;
22+
import org.apache.commons.lang3.StringUtils;
2123
import org.apache.logging.log4j.LogManager;
2224
import org.apache.logging.log4j.Logger;
25+
import org.apache.struts2.StrutsConstants;
2326
import org.apache.struts2.action.CspSettingsAware;
2427

2528
import javax.servlet.http.HttpServletRequest;
@@ -43,56 +46,82 @@
4346
*/
4447
public class DefaultCspSettings implements CspSettings {
4548

46-
private final static Logger LOG = LogManager.getLogger(DefaultCspSettings.class);
49+
private static final Logger LOG = LogManager.getLogger(DefaultCspSettings.class);
50+
private static final String NONCE_KEY = "nonce";
4751

4852
private final SecureRandom sRand = new SecureRandom();
4953

54+
private CspNonceSource nonceSource = CspNonceSource.SESSION;
55+
5056
protected String reportUri;
5157
protected String reportTo;
5258
// default to reporting mode
5359
protected String cspHeader = CSP_REPORT_HEADER;
5460

61+
@Inject(value = StrutsConstants.STRUTS_CSP_NONCE_SOURCE, required = false)
62+
public void setNonceSource(String nonceSource) {
63+
if (StringUtils.isBlank(nonceSource)) {
64+
this.nonceSource = CspNonceSource.SESSION;
65+
} else {
66+
this.nonceSource = CspNonceSource.valueOf(nonceSource.toUpperCase());
67+
}
68+
}
69+
5570
@Override
5671
public void addCspHeaders(HttpServletResponse response) {
5772
throw new UnsupportedOperationException("Unsupported implementation, use #addCspHeaders(HttpServletRequest request, HttpServletResponse response)");
5873
}
5974

6075
@Override
6176
public void addCspHeaders(HttpServletRequest request, HttpServletResponse response) {
77+
if (this.nonceSource == CspNonceSource.SESSION) {
78+
addCspHeadersWithSession(request, response);
79+
} else if (this.nonceSource == CspNonceSource.REQUEST) {
80+
addCspHeadersWithRequest(request, response);
81+
} else {
82+
LOG.warn("Unknown nonce source: {}, ignoring CSP settings", nonceSource);
83+
}
84+
}
85+
86+
private void addCspHeadersWithSession(HttpServletRequest request, HttpServletResponse response) {
6287
if (isSessionActive(request)) {
6388
LOG.trace("Session is active, applying CSP settings");
64-
associateNonceWithSession(request);
89+
request.getSession().setAttribute(NONCE_KEY, generateNonceValue());
6590
response.setHeader(cspHeader, createPolicyFormat(request));
6691
} else {
67-
LOG.trace("Session is not active, ignoring CSP settings");
92+
LOG.debug("Session is not active, ignoring CSP settings");
6893
}
6994
}
7095

96+
private void addCspHeadersWithRequest(HttpServletRequest request, HttpServletResponse response) {
97+
request.setAttribute(NONCE_KEY, generateNonceValue());
98+
response.setHeader(cspHeader, createPolicyFormat(request));
99+
}
100+
71101
private boolean isSessionActive(HttpServletRequest request) {
72102
return request.getSession(false) != null;
73103
}
74104

75-
private void associateNonceWithSession(HttpServletRequest request) {
76-
String nonceValue = Base64.getUrlEncoder().encodeToString(getRandomBytes());
77-
request.getSession().setAttribute("nonce", nonceValue);
105+
private String generateNonceValue() {
106+
return Base64.getUrlEncoder().encodeToString(getRandomBytes());
78107
}
79108

80109
protected String createPolicyFormat(HttpServletRequest request) {
81110
StringBuilder policyFormatBuilder = new StringBuilder()
82-
.append(OBJECT_SRC)
83-
.append(format(" '%s'; ", NONE))
84-
.append(SCRIPT_SRC)
85-
.append(" 'nonce-%s' ") // nonce placeholder
86-
.append(format("'%s' ", STRICT_DYNAMIC))
87-
.append(format("%s %s; ", HTTP, HTTPS))
88-
.append(BASE_URI)
89-
.append(format(" '%s'; ", NONE));
111+
.append(OBJECT_SRC)
112+
.append(format(" '%s'; ", NONE))
113+
.append(SCRIPT_SRC)
114+
.append(" 'nonce-%s' ") // nonce placeholder
115+
.append(format("'%s' ", STRICT_DYNAMIC))
116+
.append(format("%s %s; ", HTTP, HTTPS))
117+
.append(BASE_URI)
118+
.append(format(" '%s'; ", NONE));
90119

91120
if (reportUri != null) {
92121
policyFormatBuilder
93-
.append(REPORT_URI)
94-
.append(format(" %s; ", reportUri));
95-
if(reportTo != null) {
122+
.append(REPORT_URI)
123+
.append(format(" %s; ", reportUri));
124+
if (reportTo != null) {
96125
policyFormatBuilder
97126
.append(REPORT_TO)
98127
.append(format(" %s; ", reportTo));
@@ -103,7 +132,7 @@ protected String createPolicyFormat(HttpServletRequest request) {
103132
}
104133

105134
protected String getNonceString(HttpServletRequest request) {
106-
Object nonce = request.getSession().getAttribute("nonce");
135+
Object nonce = request.getSession().getAttribute(NONCE_KEY);
107136
return Objects.toString(nonce);
108137
}
109138

@@ -133,10 +162,10 @@ public void setReportTo(String reportTo) {
133162
@Override
134163
public String toString() {
135164
return "DefaultCspSettings{" +
136-
"reportUri='" + reportUri + '\'' +
137-
", reportTo='" + reportTo + '\'' +
138-
", cspHeader='" + cspHeader + '\'' +
139-
'}';
165+
"reportUri='" + reportUri + '\'' +
166+
", reportTo='" + reportTo + '\'' +
167+
", cspHeader='" + cspHeader + '\'' +
168+
'}';
140169
}
141170

142171
}

0 commit comments

Comments
 (0)