Skip to content

Commit f6cbfeb

Browse files
authored
Add CSP headers (#63)
* Added CSP, made GA work with CSP
1 parent 1ac0031 commit f6cbfeb

File tree

7 files changed

+217
-12
lines changed

7 files changed

+217
-12
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package com.stubbornjava.undertow.handlers;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.stream.Collectors;
6+
import java.util.stream.Stream;
7+
8+
import io.undertow.server.HttpHandler;
9+
import io.undertow.server.handlers.SetHeaderHandler;
10+
11+
public class ContentSecurityPolicyHandler {
12+
private static final String CSP_HEADER = "Content-Security-Policy";
13+
14+
public enum ContentSecurityPolicy {
15+
NONE("'none'"), // blocks the use of this type of resource.
16+
SELF("'self'"), // matches the current origin (but not subdomains).
17+
UNSAFE_INLINE("'unsafe-inline'"), // allows the use of inline JS and CSS.
18+
UNSAFE_EVAL("'unsafe-eval'"), // allows the use of mechanisms like eval().
19+
;
20+
21+
private final String value;
22+
ContentSecurityPolicy(String value) {
23+
this.value = value;
24+
}
25+
public String getValue() {
26+
return value;
27+
}
28+
}
29+
30+
// https://scotthelme.co.uk/content-security-policy-an-introduction/#whatcanweprotect
31+
public static class Builder {
32+
private final Map<String, String> policyMap;
33+
34+
public Builder() {
35+
this.policyMap = new HashMap<>();
36+
}
37+
38+
public Builder defaultSrc(ContentSecurityPolicy policy) {
39+
policyMap.put("default-src", policy.getValue());
40+
return this;
41+
}
42+
43+
public Builder defaultSrc(String... policies) {
44+
policyMap.put("default-src", join(policies));
45+
return this;
46+
}
47+
48+
public Builder scriptSrc(ContentSecurityPolicy policy) {
49+
policyMap.put("script-src", policy.getValue());
50+
return this;
51+
}
52+
53+
public Builder scriptSrc(String... policies) {
54+
policyMap.put("script-src", join(policies));
55+
return this;
56+
}
57+
58+
public Builder objectSrc(ContentSecurityPolicy policy) {
59+
policyMap.put("object-src", policy.getValue());
60+
return this;
61+
}
62+
63+
public Builder objectSrc(String... policies) {
64+
policyMap.put("object-src", join(policies));
65+
return this;
66+
}
67+
68+
public Builder styleSrc(ContentSecurityPolicy policy) {
69+
policyMap.put("style-src", policy.getValue());
70+
return this;
71+
}
72+
73+
public Builder styleSrc(String... policies) {
74+
policyMap.put("style-src", join(policies));
75+
return this;
76+
}
77+
78+
public Builder imgSrc(ContentSecurityPolicy policy) {
79+
policyMap.put("img-src", policy.getValue());
80+
return this;
81+
}
82+
83+
public Builder imgSrc(String... policies) {
84+
policyMap.put("img-src", join(policies));
85+
return this;
86+
}
87+
88+
public Builder mediaSrc(ContentSecurityPolicy policy) {
89+
policyMap.put("media-src", policy.getValue());
90+
return this;
91+
}
92+
93+
public Builder mediaSrc(String... policies) {
94+
policyMap.put("media-src", join(policies));
95+
return this;
96+
}
97+
98+
public Builder frameSrc(ContentSecurityPolicy policy) {
99+
policyMap.put("frame-src", policy.getValue());
100+
return this;
101+
}
102+
103+
public Builder frameSrc(String... policies) {
104+
policyMap.put("frame-src", join(policies));
105+
return this;
106+
}
107+
108+
public Builder fontSrc(ContentSecurityPolicy policy) {
109+
policyMap.put("font-src", policy.getValue());
110+
return this;
111+
}
112+
113+
public Builder fontSrc(String... policies) {
114+
policyMap.put("font-src", join(policies));
115+
return this;
116+
}
117+
118+
public Builder connectSrc(ContentSecurityPolicy policy) {
119+
policyMap.put("connect-src", policy.getValue());
120+
return this;
121+
}
122+
123+
public Builder connectSrc(String... policies) {
124+
policyMap.put("connect-src", join(policies));
125+
return this;
126+
}
127+
128+
public Builder formAction(ContentSecurityPolicy policy) {
129+
policyMap.put("form-action", policy.getValue());
130+
return this;
131+
}
132+
133+
public Builder formAction(String... policies) {
134+
policyMap.put("form-action", join(policies));
135+
return this;
136+
}
137+
138+
public Builder sandbox(ContentSecurityPolicy policy) {
139+
policyMap.put("sandbox", policy.getValue());
140+
return this;
141+
}
142+
143+
public Builder sandbox(String... policies) {
144+
policyMap.put("sandbox", join(policies));
145+
return this;
146+
}
147+
148+
public Builder scriptNonce(ContentSecurityPolicy policy) {
149+
policyMap.put("script-nonce", policy.getValue());
150+
return this;
151+
}
152+
153+
public Builder scriptNonce(String... policies) {
154+
policyMap.put("script-nonce", join(policies));
155+
return this;
156+
}
157+
158+
public Builder pluginTypes(ContentSecurityPolicy policy) {
159+
policyMap.put("plugin-types", policy.getValue());
160+
return this;
161+
}
162+
163+
public Builder pluginTypes(String... policies) {
164+
policyMap.put("plugin-types", join(policies));
165+
return this;
166+
}
167+
168+
public Builder reflectedXss(ContentSecurityPolicy policy) {
169+
policyMap.put("reflected-xss", policy.getValue());
170+
return this;
171+
}
172+
173+
public Builder reflectedXss(String... policies) {
174+
policyMap.put("reflected-xss", join(policies));
175+
return this;
176+
}
177+
178+
public Builder reportUri(String uri) {
179+
policyMap.put("report-uri", uri);
180+
return this;
181+
}
182+
183+
public HttpHandler build(HttpHandler delegate) {
184+
String policy = policyMap.entrySet()
185+
.stream()
186+
.map(entry -> entry.getKey() + " " + entry.getValue())
187+
.collect(Collectors.joining("; "));
188+
return new SetHeaderHandler(delegate, CSP_HEADER, policy);
189+
}
190+
191+
private String join(String... strings) {
192+
return Stream.of(strings).collect(Collectors.joining(" "));
193+
}
194+
}
195+
}

stubbornjava-webapp/src/main/java/com/stubbornjava/webapp/StubbornJavaWebApp.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import com.stubbornjava.common.seo.SitemapRoutes;
1111
import com.stubbornjava.common.undertow.SimpleServer;
1212
import com.stubbornjava.common.undertow.handlers.CustomHandlers;
13+
import com.stubbornjava.undertow.handlers.ContentSecurityPolicyHandler;
14+
import com.stubbornjava.undertow.handlers.ContentSecurityPolicyHandler.ContentSecurityPolicy;
1315
import com.stubbornjava.undertow.handlers.MiddlewareBuilder;
1416
import com.stubbornjava.undertow.handlers.ReferrerPolicyHandlers.ReferrerPolicy;
1517
import com.stubbornjava.webapp.guide.GuideRoutes;
@@ -31,9 +33,20 @@ private static HttpHandler exceptionHandler(HttpHandler next) {
3133
.addExceptionHandler(Throwable.class, PageRoutes::error);
3234
}
3335

36+
private static HttpHandler contentSecurityPolicy(HttpHandler delegate) {
37+
return new ContentSecurityPolicyHandler.Builder()
38+
.defaultSrc(ContentSecurityPolicy.SELF)
39+
.scriptSrc("'self'", "https://www.google-analytics.com")
40+
.imgSrc("'self'", "https://www.google-analytics.com")
41+
.connectSrc("'self'", "https://www.google-analytics.com")
42+
.styleSrc(ContentSecurityPolicy.SELF.getValue(), ContentSecurityPolicy.UNSAFE_INLINE.getValue())
43+
.build(delegate);
44+
}
45+
3446
private static HttpHandler wrapWithMiddleware(HttpHandler next) {
3547
return MiddlewareBuilder.begin(PageRoutes::redirector)
3648
.next(handler -> CustomHandlers.securityHeaders(handler, ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN))
49+
.next(StubbornJavaWebApp::contentSecurityPolicy)
3750
.next(CustomHandlers::gzip)
3851
.next(BlockingHandler::new)
3952
.next(ex -> CustomHandlers.accessLog(ex, logger))

stubbornjava-webapp/ui/src/common/_base-layout.hbs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
{{> content}}
1111
{{> templates/src/widgets/footer/footer}}
1212
{{> templates/src/common/scripts}}
13-
{{> templates/src/common/google-analytics}}
1413
{{#> extra-scripts}}{{/extra-scripts}}
1514
</body>
1615
</html>

stubbornjava-webapp/ui/src/common/_error-layout.hbs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@
77
<body class="error-bg">
88
{{> content}}
99
{{> templates/src/common/scripts}}
10-
{{> templates/src/common/google-analytics}}
1110
</body>
1211
</html>

stubbornjava-webapp/ui/src/common/common.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import $ from 'jquery';
22

3+
import './google-analytics.js';
4+
35
import './3rdparty.js';
46

57
import './common.scss';

stubbornjava-webapp/ui/src/common/google-analytics.hbs

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
3+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
4+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
5+
6+
ga('create', 'UA-89603048-1', 'auto');
7+
ga('send', 'pageview');

0 commit comments

Comments
 (0)