Skip to content

Commit f270eb3

Browse files
committed
CSP: extract logic for adding headers to a separate class.
Addressed to #226 No functional changes.
1 parent d453db8 commit f270eb3

File tree

2 files changed

+127
-91
lines changed

2 files changed

+127
-91
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright (C) 2009-2017 Slava Semushin <slava.semushin@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; either version 2 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
package ru.mystamps.web.support.spring.security;
19+
20+
import javax.servlet.http.HttpServletRequest;
21+
import javax.servlet.http.HttpServletResponse;
22+
23+
import org.springframework.security.web.header.HeaderWriter;
24+
25+
import ru.mystamps.web.Url;
26+
27+
/**
28+
* Implementation of {@link HeaderWriter} that is adding CSP header depending on the current URL.
29+
*/
30+
class ContentSecurityPolicyHeaderWriter implements HeaderWriter {
31+
32+
private static final String COLLECTION_INFO_PAGE_PATTERN =
33+
Url.INFO_COLLECTION_PAGE.replace("{slug}", "");
34+
35+
// default policy prevents loading resources from any source
36+
private static final String DEFAULT_SRC = "default-src 'none'";
37+
38+
// - 'self' is required for uploaded images and its previews
39+
// - 'https://cdn.rawgit.com' is required by languages.png (TODO: GH #246)
40+
// - 'https://raw.githubusercontent.com' is required by languages.png
41+
// CheckStyle: ignore LineLength for next 1 line
42+
private static final String IMG_SRC = "img-src 'self' https://cdn.rawgit.com https://raw.githubusercontent.com";
43+
44+
// - 'self' is required by glyphicons-halflings-regular.woff2 from bootstrap
45+
private static final String FONT_SRC = "font-src 'self'";
46+
47+
// CheckStyle: ignore LineLength for next 1 line
48+
private static final String REPORT_URI = "report-uri https://mystamps.report-uri.io/r/default/csp/reportOnly";
49+
50+
// - 'self' is required for our own CSS files
51+
// - 'https://cdn.rawgit.com' is required by languages.min.css (TODO: GH #246)
52+
// - 'sha256-Dpm...' is required for 'box-shadow: none; border: 0px;' inline CSS
53+
// that are using on /series/add and /series/{id} pages.
54+
private static final String STYLE_SRC =
55+
"style-src 'self' https://cdn.rawgit.com"
56+
+ " 'sha256-DpmxvnMJIlwkpmmAANZYNzmyfnX2PQCBDO4CB2BFjzU='";
57+
58+
// - 'https://www.gstatic.com' is required by Google Charts
59+
// - 'sha256-/kX...' is required for 'overflow: hidden;' inline CSS that is using
60+
// by Google Charts.
61+
private static final String STYLE_COLLECTION_INFO =
62+
" https://www.gstatic.com 'sha256-/kXZODfqoc2myS1eI6wr0HH8lUt+vRhW8H/oL+YJcMg='";
63+
64+
// - 'self' is required for our own JS files
65+
// - 'unsafe-inline' is required by jquery.min.js (that is using code inside of
66+
// event handlers. We can't use hashing algorithms because they aren't supported
67+
// for handlers. In future, we should get rid of jQuery or use
68+
// 'unsafe-hashed-attributes' from CSP3. Details:
69+
// https://github.com/jquery/jquery/blob/d71f6a53927ad02d/jquery.js#L1441-L1447
70+
// and https://w3c.github.io/webappsec-csp/#unsafe-hashed-attributes-usage)
71+
private static final String SCRIPT_SRC = "script-src 'self' 'unsafe-inline'";
72+
73+
// - 'unsafe-eval' is required by loader.js from Google Charts
74+
// - 'https://www.gstatic.com' is required by Google Charts
75+
private static final String SCRIPT_COLLECTION_INFO = " 'unsafe-eval' https://www.gstatic.com";
76+
77+
// - 'self' is required for AJAX requests from our scripts (country suggestions)
78+
private static final String CONNECT_SRC = "connect-src 'self'";
79+
80+
private static final char SEPARATOR = ';';
81+
82+
private static final int MIN_HEADER_LENGTH =
83+
DEFAULT_SRC.length()
84+
+ IMG_SRC.length()
85+
+ FONT_SRC.length()
86+
+ REPORT_URI.length()
87+
+ STYLE_SRC.length()
88+
+ SCRIPT_SRC.length()
89+
+ CONNECT_SRC.length()
90+
// number of separators between directives
91+
+ 6;
92+
93+
@Override
94+
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
95+
response.setHeader("Content-Security-Policy-Report-Only", constructDirectives(request));
96+
}
97+
98+
private static String constructDirectives(HttpServletRequest request) {
99+
boolean onCollectionInfoPage =
100+
request.getRequestURI().startsWith(COLLECTION_INFO_PAGE_PATTERN);
101+
102+
StringBuilder sb = new StringBuilder(MIN_HEADER_LENGTH);
103+
104+
sb.append(DEFAULT_SRC).append(SEPARATOR)
105+
.append(IMG_SRC).append(SEPARATOR)
106+
.append(FONT_SRC).append(SEPARATOR)
107+
.append(REPORT_URI).append(SEPARATOR)
108+
.append(STYLE_SRC);
109+
110+
if (onCollectionInfoPage) {
111+
sb.append(STYLE_COLLECTION_INFO);
112+
}
113+
sb.append(SEPARATOR)
114+
.append(SCRIPT_SRC);
115+
116+
if (onCollectionInfoPage) {
117+
sb.append(SCRIPT_COLLECTION_INFO);
118+
}
119+
120+
sb.append(SEPARATOR)
121+
.append(CONNECT_SRC);
122+
123+
return sb.toString();
124+
}
125+
126+
}

src/main/java/ru/mystamps/web/support/spring/security/SecurityConfig.java

Lines changed: 1 addition & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@
4646
import org.springframework.security.crypto.password.PasswordEncoder;
4747
import org.springframework.security.web.access.AccessDeniedHandler;
4848
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
49-
import org.springframework.security.web.header.HeaderWriter;
50-
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
51-
import org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter;
52-
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
53-
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
5449

5550
import ru.mystamps.web.Url;
5651
import ru.mystamps.web.config.ServicesConfig;
@@ -122,22 +117,7 @@ protected void configure(HttpSecurity http) throws Exception {
122117
.disable()
123118
.headers()
124119
.defaultsDisabled() // TODO
125-
.addHeaderWriter(
126-
new DelegatingRequestMatcherHeaderWriter(
127-
new NegatedRequestMatcher(
128-
new AntPathRequestMatcher(
129-
Url.INFO_COLLECTION_PAGE.replace("{slug}", "**")
130-
)
131-
),
132-
getCspForCommonPages()
133-
)
134-
)
135-
.addHeaderWriter(
136-
new DelegatingRequestMatcherHeaderWriter(
137-
new AntPathRequestMatcher(Url.INFO_COLLECTION_PAGE.replace("{slug}", "**")),
138-
getCspForCollectionInfoPage()
139-
)
140-
);
120+
.addHeaderWriter(new ContentSecurityPolicyHeaderWriter());
141121
}
142122

143123
// Used in ServicesConfig.getUserService()
@@ -198,74 +178,4 @@ private UserDetailsService getUserDetailsService() {
198178
return new CustomUserDetailsService(servicesConfig.getUserService());
199179
}
200180

201-
private HeaderWriter getCspForCommonPages() {
202-
ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter(
203-
// default policy prevents loading resources from any source
204-
"default-src 'none'; "
205-
// 'self' is required for: our own CSS files
206-
// 'https://cdn.rawgit.com' is required for: languages.min.css (TODO: GH #246)
207-
// 'sha256-Dpm...' is required for: 'box-shadow: none; border: 0px;' inline CSS
208-
// that are using on /series/add and /series/{id} pages.
209-
+ "style-src 'self' https://cdn.rawgit.com "
210-
+ "'sha256-DpmxvnMJIlwkpmmAANZYNzmyfnX2PQCBDO4CB2BFjzU='; "
211-
// 'self' is required for: our own JS files
212-
// 'unsafe-inline' is required for: jquery.min.js (that is using code inside of
213-
// event handlers. We can't use hashing algorithms because they aren't supported
214-
// for handlers. In future, we should get rid of jQuery or use
215-
// 'unsafe-hashed-attributes' from CSP3. Details:
216-
// https://github.com/jquery/jquery/blob/d71f6a53927ad02d/jquery.js#L1441-L1447
217-
// and https://w3c.github.io/webappsec-csp/#unsafe-hashed-attributes-usage)
218-
+ "script-src 'self' 'unsafe-inline'; "
219-
// 'self' is required for: AJAX requests from our scripts (country suggestions)
220-
+ "connect-src 'self'; "
221-
// 'self' is required for: uploaded images and its previews
222-
// 'https://cdn.rawgit.com' is required for: languages.png (TODO: GH #246)
223-
// 'https://raw.githubusercontent.com' is required for: languages.png
224-
+ "img-src 'self' https://cdn.rawgit.com https://raw.githubusercontent.com; "
225-
// 'self' is required for: glyphicons-halflings-regular.woff2 from bootstrap
226-
+ "font-src 'self'; "
227-
+ "report-uri https://mystamps.report-uri.io/r/default/csp/reportOnly"
228-
);
229-
writer.setReportOnly(true);
230-
return writer;
231-
}
232-
233-
private HeaderWriter getCspForCollectionInfoPage() {
234-
ContentSecurityPolicyHeaderWriter writer = new ContentSecurityPolicyHeaderWriter(
235-
// default policy prevents loading resources from any source
236-
"default-src 'none'; "
237-
// 'self' is required for: our own CSS files
238-
// 'https://cdn.rawgit.com' is required for: languages.min.css (TODO: GH #246)
239-
// 'https://www.gstatic.com' is required for: Google Charts on collection page.
240-
// 'sha256-Dpm...' is required for: 'box-shadow: none; border: 0px;' inline CSS
241-
// that are using on /series/add and /series/{id} pages.
242-
// 'sha256-/kX...' is required for: 'overflow: hidden;' inline CSS that is using
243-
// bg Google Charts on collection page.
244-
+ "style-src 'self' https://cdn.rawgit.com https://www.gstatic.com "
245-
+ "'sha256-DpmxvnMJIlwkpmmAANZYNzmyfnX2PQCBDO4CB2BFjzU=' "
246-
+ "'sha256-/kXZODfqoc2myS1eI6wr0HH8lUt+vRhW8H/oL+YJcMg='; "
247-
// 'self' is required for: our own JS files
248-
// 'unsafe-inline' is required for: jquery.min.js (that is using code inside of
249-
// event handlers. We can't use hashing algorithms because they aren't supported
250-
// for handlers. In future, we should get rid of jQuery or use
251-
// 'unsafe-hashed-attributes' from CSP3. Details:
252-
// https://github.com/jquery/jquery/blob/d71f6a53927ad02d/jquery.js#L1441-L1447
253-
// and https://w3c.github.io/webappsec-csp/#unsafe-hashed-attributes-usage)
254-
// 'unsafe-eval' is required for: loader.js (for Google Charts)
255-
// 'https://www.gstatic.com' is required for: Google Charts on collection page.
256-
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.gstatic.com; "
257-
// 'self' is required for: AJAX requests from our scripts (country suggestions)
258-
+ "connect-src 'self'; "
259-
// 'self' is required for: uploaded images and its previews
260-
// 'https://cdn.rawgit.com' is required for: languages.png (TODO: GH #246)
261-
// 'https://raw.githubusercontent.com' is required for: languages.png
262-
+ "img-src 'self' https://cdn.rawgit.com https://raw.githubusercontent.com; "
263-
// 'self' is required for: glyphicons-halflings-regular.woff2 from bootstrap
264-
+ "font-src 'self'; "
265-
+ "report-uri https://mystamps.report-uri.io/r/default/csp/reportOnly"
266-
);
267-
writer.setReportOnly(true);
268-
return writer;
269-
}
270-
271181
}

0 commit comments

Comments
 (0)