mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-03-01 15:17:07 +08:00
add CSP stuff, remove some debug outputs
Signed-off-by: MiniDigger <admin@minidigger.me>
This commit is contained in:
parent
7644980ec4
commit
ca013ce231
@ -125,13 +125,11 @@ export default {
|
||||
watch: {
|
||||
preCooked() {
|
||||
this.$nextTick(() => {
|
||||
console.log('running');
|
||||
window.admonition();
|
||||
});
|
||||
},
|
||||
previewContent() {
|
||||
this.$nextTick(() => {
|
||||
console.log('running');
|
||||
window.admonition();
|
||||
});
|
||||
},
|
||||
|
@ -186,7 +186,7 @@ library.add(
|
||||
faArrowsAltV,
|
||||
faUndo,
|
||||
faCopy,
|
||||
faBars,
|
||||
faBars
|
||||
);
|
||||
|
||||
dom.watch();
|
||||
|
@ -15,7 +15,6 @@ $(function () {
|
||||
var needsModal = $(this).data('modal');
|
||||
var spinner = $('button[data-project="' + project + '"]').find('[data-fa-i2svg]');
|
||||
toggleSpinner(spinner.toggleClass(ICON));
|
||||
console.log(needsModal);
|
||||
if (needsModal) {
|
||||
$('.modal-title').html($(this).text().trim() + ': comment');
|
||||
$('#modal-visibility-comment').modal('show');
|
||||
|
@ -39,6 +39,9 @@ module.exports = {
|
||||
devServer: {
|
||||
port: 8081,
|
||||
hot: process.env.NODE_ENV === 'development',
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "http://localhost:8080"
|
||||
}
|
||||
},
|
||||
chainWebpack: (config) => {
|
||||
// clear default
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.papermc.hangar.controller.filters;
|
||||
|
||||
import io.papermc.hangar.config.hangar.HangarConfig;
|
||||
import io.papermc.hangar.controller.filters.ContentSecurityPolicyFilter.CSP.PolicyBuilder;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -12,32 +14,327 @@ import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Component
|
||||
public class ContentSecurityPolicyFilter extends OncePerRequestFilter {
|
||||
|
||||
public final HangarConfig hangarConfig;
|
||||
|
||||
// TODO check this
|
||||
private static final String CSP = "default-src 'self' https://google-analytics.com {additional-uris} fonts.googleapis.com; style-src fonts.googleapis.com 'self' {additional-uris} 'unsafe-inline'; font-src fonts.gstatic.com; script-src {additional-uris} 'self' 'nonce-{nonce}' 'unsafe-eval'; img-src 'self' data: papermc.io paper.readthedocs.io {additional-uris} {auth-uri}; manifest-src {manifest-uri}; prefetch-src {prefetch-uri}; media-src {prefetch-uri}; object-src 'none'; block-all-mixed-content; frame-ancestors 'none'; base-uri 'none'";
|
||||
private final String cspHeader;
|
||||
|
||||
@Autowired
|
||||
public ContentSecurityPolicyFilter(HangarConfig hangarConfig) {
|
||||
this.hangarConfig = hangarConfig;
|
||||
PolicyBuilder builder = new PolicyBuilder();
|
||||
|
||||
builder.default_src(CSP.SELF, "https://google-analytics.com", "https://fonts.gstatic.com", "https://fonts.googleapis.com")
|
||||
.style_src(CSP.SELF, "https://fonts.googleapis.com", CSP.UNSAFE_INLINE)
|
||||
.font_src("fonts.gstatic.com")
|
||||
.script_src(CSP.SELF, "'nonce-{nonce}'")
|
||||
.img_src(CSP.SELF, hangarConfig.getAuthUrl(), "https://www.google-analytics.com", "data: papermc.io paper.readthedocs.io")
|
||||
.manifest_src(CSP.SELF)
|
||||
.prefetch_src(CSP.SELF, "https://fonts.googleapis.com") // isnt implemented yet -> default_src
|
||||
.connect_src(CSP.SELF, "https://www.google-analytics.com", "https://stats.g.doubleclick.net")
|
||||
.media_src(CSP.SELF)
|
||||
.object_src(CSP.NONE)
|
||||
.frame_ancestor(CSP.NONE)
|
||||
.base_uri(CSP.NONE)
|
||||
.block_all_mixed_content();
|
||||
|
||||
if (hangarConfig.isUseWebpack()) {
|
||||
String webpack = "http://localhost:8081";
|
||||
String webSocket = "ws://*:8081";
|
||||
String socketIo = "http://*:8081";
|
||||
builder.default_src(webpack, socketIo, webSocket)
|
||||
.style_src(webpack, socketIo, webSocket)
|
||||
.script_src(webpack, socketIo, webSocket, CSP.UNSAFE_EVAL)
|
||||
.img_src(webpack, socketIo, webSocket)
|
||||
.prefetch_src(webpack)
|
||||
.connect_src(socketIo, webSocket)
|
||||
.manifest_src("http://localhost:8081/manifest/manifest.json")
|
||||
.media_src(webpack);
|
||||
}
|
||||
|
||||
cspHeader = builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
|
||||
String CSP_NONCE = RandomStringUtils.randomAlphanumeric(64);
|
||||
response.addHeader(
|
||||
"Content-Security-Policy",
|
||||
CSP
|
||||
.replace("{additional-uris}", hangarConfig.isUseWebpack() ? "http://localhost:8081 http://*:8081 ws://*:8081" : "") // TODO idk how to get the local IP to put here... without those last two entries, there are a bunch of console errors. Maybe you can figure it out Mini.
|
||||
.replace("{auth-uri}", hangarConfig.getAuthUrl())
|
||||
.replace("{manifest-uri}", hangarConfig.isUseWebpack() ? "http://localhost:8081/manifest/manifest.json" : "'self'")
|
||||
.replace("{prefetch-uri}", hangarConfig.isUseWebpack() ? "http://localhost:8081" : "'self'")
|
||||
.replace("{nonce}", CSP_NONCE));
|
||||
request.setAttribute("nonce", CSP_NONCE);
|
||||
String nonce = RandomStringUtils.randomAlphanumeric(64);
|
||||
response.addHeader(CSP.Header.SECURE,cspHeader.replace("{nonce}", nonce));
|
||||
request.setAttribute("nonce", nonce);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copyright (C) 2013 salesforce.com, inc.
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
/**
|
||||
* This class provides functionality for building Content Security Policy 1.0
|
||||
* (CSP) HTTP response headers as described in <a href="http://www.w3.org/TR/CSP/">
|
||||
* the W3C Content Security Policy 1.0 spec</a>.
|
||||
*
|
||||
* Use PolicyBuilder to build a header value to attach to an HTTP response.
|
||||
*
|
||||
* Example Usage:
|
||||
*
|
||||
* To set a header that <em>only reports</em> all resource loading:
|
||||
* <code>
|
||||
PolicyBuilder p = new PolicyBuilder();
|
||||
p
|
||||
.default_src(CSP.NONE)
|
||||
.report_uri(CSPReporterServlet.URL);
|
||||
response.setHeader(CSP.Header.REPORT_ONLY, p.build());
|
||||
* </code>
|
||||
*
|
||||
* To set a header that <em>disallows</em> loading of scripts <em>except</em>
|
||||
* from social network widgets:
|
||||
* <code>
|
||||
PolicyBuilder p = new PolicyBuilder();
|
||||
p
|
||||
.script_src(
|
||||
"https://apis.google.com",
|
||||
"https://platform.twitter.com")
|
||||
.frame_src(
|
||||
"https://plusone.google.com",
|
||||
"https://facebook.com",
|
||||
"https://platform.twitter.com");
|
||||
response.setHeader(CSP.Header.SECURE, p.build());
|
||||
* </code>
|
||||
*/
|
||||
public static class CSP {
|
||||
|
||||
private CSP() {
|
||||
// This class is only static methods and should not be instantiated
|
||||
}
|
||||
|
||||
public static final class Header {
|
||||
private Header() {
|
||||
// This class is only static methods and should not be instantiated
|
||||
}
|
||||
|
||||
public static final String SECURE = "Content-Security-Policy";
|
||||
public static final String REPORT_ONLY = "Content-Security-Policy-Report-Only";
|
||||
}
|
||||
|
||||
public enum Directive {
|
||||
DEFAULT("default-src"),
|
||||
SCRIPT("script-src"),
|
||||
OBJECT("object-src"),
|
||||
STYLE("style-src"),
|
||||
IMG("img-src"),
|
||||
MEDIA("media-src"),
|
||||
FRAME_ANCESTOR("frame-ancestors"), // Sites allowed to frame this page
|
||||
FRAME_SRC("frame-src"), // Sites this page is allowed to frame
|
||||
FONT("font-src"),
|
||||
CONNECT("connect-src"),
|
||||
SANDBOX("sandbox"),
|
||||
REPORT_URI("report-uri"),
|
||||
PLUGIN_TYPES("plugin-types"),
|
||||
BASE_URI("base-uri"),
|
||||
REFERRER("referrer"),
|
||||
MANIFEST("manifest-src"),
|
||||
PREFETCH("prefetch-src"),
|
||||
UPGRADE_INSECURE_REQUESTS("upgrade-insecure-requests"),
|
||||
BLOCK_ALL_MIXED_CONTENT("block-all-mixed-content");
|
||||
|
||||
private String directive;
|
||||
|
||||
Directive(String directive) {
|
||||
this.directive = directive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return directive;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special value for allowing a resource type from the same domain as
|
||||
* served the initial response.
|
||||
*/
|
||||
public static final String SELF = "'self'";
|
||||
|
||||
/**
|
||||
* Special value for disallowing a resource type from any domain.
|
||||
*/
|
||||
public static final String NONE = "'none'";
|
||||
|
||||
/**
|
||||
* Special value for allowing a resource type from any domain.
|
||||
*/
|
||||
public static final String ALL = "*";
|
||||
|
||||
/**
|
||||
* Special value for allowing inline resource inclusion (such as
|
||||
* <style> or <script>).
|
||||
*
|
||||
* Not recommended.
|
||||
*/
|
||||
public static final String UNSAFE_INLINE = "'unsafe-inline'";
|
||||
|
||||
/**
|
||||
* Special value for allowing <code>eval()</code> of JavaScript.
|
||||
*
|
||||
* Not recommended.
|
||||
*/
|
||||
public static final String UNSAFE_EVAL = "'unsafe-eval'";
|
||||
|
||||
/**
|
||||
* Fluent interface for building Content Security Policy headers.
|
||||
*
|
||||
* See {@link CSP} for example usage.
|
||||
*/
|
||||
public static class PolicyBuilder {
|
||||
private static final Set<String> DIRECTIVE_WITHOUT_VALUE_PLACEHOLDER = Collections.singleton("");
|
||||
|
||||
/**
|
||||
* This uses {@link java.util.Set}s to enforce unique elements in
|
||||
* source-list and ancestor-source-list directive values.
|
||||
*
|
||||
* Implementation note: {@link java.util.LinkedHashSet} maintains output
|
||||
* parity for the sake of existing tests that expect output in a specific order.
|
||||
*/
|
||||
private final EnumMap<Directive, Set<String>> directives = new EnumMap<>(Directive.class);
|
||||
|
||||
public PolicyBuilder default_src(String... sources) {
|
||||
return extend(Directive.DEFAULT, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder script_src(String... sources) {
|
||||
return extend(Directive.SCRIPT, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder object_src(String... sources) {
|
||||
return extend(Directive.OBJECT, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder style_src(String... sources) {
|
||||
return extend(Directive.STYLE, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder img_src(String... sources) {
|
||||
return extend(Directive.IMG, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder media_src(String... sources) {
|
||||
return extend(Directive.MEDIA, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder frame_src(String... sources) {
|
||||
return extend(Directive.FRAME_SRC, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder frame_ancestor(String... sources) {
|
||||
return extend(Directive.FRAME_ANCESTOR, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder font_src(String... sources) {
|
||||
return extend(Directive.FONT, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder connect_src(String... sources) {
|
||||
return extend(Directive.CONNECT, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder manifest_src(String... sources) {
|
||||
return extend(Directive.MANIFEST, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder prefetch_src(String... sources) {
|
||||
return extend(Directive.PREFETCH, sources);
|
||||
}
|
||||
|
||||
public PolicyBuilder sandbox(String... flags) {
|
||||
directives.put(Directive.SANDBOX, new LinkedHashSet<>(Arrays.asList(flags)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyBuilder report_uri(String... uris) {
|
||||
// If the Report URI is empty or null, don't add report-uri directive at all
|
||||
Set<String> reportURIs = Arrays.stream(uris).filter(StringUtils::isNotBlank).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
if (!reportURIs.isEmpty()) {
|
||||
directives.put(Directive.REPORT_URI, reportURIs);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyBuilder plugin_types(String... pluginTypes) {
|
||||
return extend(Directive.PLUGIN_TYPES, pluginTypes);
|
||||
}
|
||||
|
||||
public PolicyBuilder base_uri(String... uris) {
|
||||
return extend(Directive.BASE_URI, uris);
|
||||
}
|
||||
|
||||
public PolicyBuilder referrer(String referrer) {
|
||||
directives.put(Directive.REFERRER, new LinkedHashSet<>(Collections.singletonList(referrer)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyBuilder upgrade_insecure_requests() {
|
||||
// upgrade-insecure-requests does not have an associated value
|
||||
directives.put(Directive.UPGRADE_INSECURE_REQUESTS, DIRECTIVE_WITHOUT_VALUE_PLACEHOLDER);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PolicyBuilder block_all_mixed_content() {
|
||||
// block-all-mixed-content does not have an associated value
|
||||
directives.put(Directive.BLOCK_ALL_MIXED_CONTENT, DIRECTIVE_WITHOUT_VALUE_PLACEHOLDER);
|
||||
return this;
|
||||
}
|
||||
|
||||
private PolicyBuilder extend(Directive directive, String... sources) {
|
||||
Set<String> set = directives.get(directive);
|
||||
if (set == null) {
|
||||
directives.put(directive, new LinkedHashSet<>(Arrays.asList(sources)));
|
||||
} else {
|
||||
set.addAll(Arrays.asList(sources));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (Directive dir : directives.keySet()) {
|
||||
Set<String> values = directives.get(dir);
|
||||
if (!values.isEmpty()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append("; ");
|
||||
}
|
||||
|
||||
sb.append(dir);
|
||||
|
||||
for (String value : values) {
|
||||
sb.append(' ').append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user