forked from MapComplete/MapComplete
Security: calculate connect-source of specialVisualisations by argument, if applicable
This commit is contained in:
parent
e4aedc9696
commit
29b1f5be4d
3 changed files with 38 additions and 9 deletions
|
@ -15,6 +15,7 @@ import { ImmutableStore } from "../src/Logic/UIEventSource"
|
|||
import * as crypto from "crypto"
|
||||
import * as eli from "../src/assets/editor-layer-index.json"
|
||||
import * as eli_global from "../src/assets/global-raster-layers.json"
|
||||
import ValidationUtils from "../src/Models/ThemeConfig/Conversion/ValidationUtils"
|
||||
|
||||
const sharp = require("sharp")
|
||||
const template = readFileSync("theme.html", "utf8")
|
||||
|
@ -264,6 +265,7 @@ async function eliUrls(): Promise<string[]> {
|
|||
|
||||
async function generateCsp(
|
||||
layout: LayoutConfig,
|
||||
layoutJson: LayoutConfigJson,
|
||||
options: {
|
||||
scriptSrcs: string[]
|
||||
}
|
||||
|
@ -275,9 +277,20 @@ async function generateCsp(
|
|||
Constants.nominatimEndpoint,
|
||||
"https://api.openstreetmap.org",
|
||||
"https://pietervdvn.goatcounter.com",
|
||||
]
|
||||
.concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls))
|
||||
.concat(...(await eliUrls()))
|
||||
].concat(...(await eliUrls()))
|
||||
|
||||
const usedSpecialVisualisations = ValidationUtils.getSpecialVisualisationsWithArgs(layoutJson)
|
||||
for (const usedSpecialVisualisation of usedSpecialVisualisations) {
|
||||
if (typeof usedSpecialVisualisation === "string") {
|
||||
continue
|
||||
}
|
||||
const neededUrls = usedSpecialVisualisation.func.needsUrls
|
||||
if (typeof neededUrls === "function") {
|
||||
apiUrls.push(...neededUrls(usedSpecialVisualisation.args))
|
||||
} else {
|
||||
apiUrls.push(...neededUrls)
|
||||
}
|
||||
}
|
||||
|
||||
const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
||||
const hosts = new Set<string>()
|
||||
|
@ -299,6 +312,10 @@ async function generateCsp(
|
|||
}
|
||||
}
|
||||
|
||||
if (hosts.has("*")) {
|
||||
throw "* is not allowed as connect-src"
|
||||
}
|
||||
|
||||
const connectSrc = Array.from(hosts).sort()
|
||||
|
||||
const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem))
|
||||
|
@ -317,7 +334,7 @@ async function generateCsp(
|
|||
"default-src": "'self'",
|
||||
"child-src": "'self' blob: ",
|
||||
"img-src": "* data:", // maplibre depends on 'data:' to load
|
||||
"connect-src": connectSrc.join(" "),
|
||||
"connect-src": onnectSrc.join(" "),
|
||||
"report-to": "https://report.mapcomplete.org/csp",
|
||||
"worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
|
||||
"style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours
|
||||
|
@ -344,7 +361,13 @@ const removeOtherLanguagesHash = crypto
|
|||
.update(removeOtherLanguages)
|
||||
.digest("base64")
|
||||
|
||||
async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) {
|
||||
async function createLandingPage(
|
||||
layout: LayoutConfig,
|
||||
layoutJson: LayoutConfigJson,
|
||||
manifest,
|
||||
whiteIcons,
|
||||
alreadyWritten
|
||||
) {
|
||||
Locale.language.setData(layout.language[0])
|
||||
const targetLanguage = layout.language[0]
|
||||
const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"')
|
||||
|
@ -428,7 +451,7 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr
|
|||
.replace(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
|
||||
.replace(
|
||||
/<!-- CSP -->/,
|
||||
await generateCsp(layout, {
|
||||
await generateCsp(layout, layoutJson, {
|
||||
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
|
||||
})
|
||||
)
|
||||
|
@ -518,7 +541,13 @@ async function main(): Promise<void> {
|
|||
writeFile("public/" + manifestLocation, manif, err)
|
||||
|
||||
// Create a landing page for the given theme
|
||||
const landing = await createLandingPage(layout, manifest, whiteIcons, alreadyWritten)
|
||||
const landing = await createLandingPage(
|
||||
layout,
|
||||
layoutConfigJson,
|
||||
manifest,
|
||||
whiteIcons,
|
||||
alreadyWritten
|
||||
)
|
||||
|
||||
writeFile(enc(layout.id) + ".html", landing, err)
|
||||
await createIndexFor(layout)
|
||||
|
|
|
@ -88,7 +88,7 @@ export interface SpecialVisualization {
|
|||
readonly funcName: string
|
||||
readonly docs: string | BaseUIElement
|
||||
readonly example?: string
|
||||
readonly needsUrls: string[]
|
||||
readonly needsUrls: string[] | ((args: string[]) => string)
|
||||
|
||||
/**
|
||||
* Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included
|
||||
|
|
|
@ -1454,7 +1454,7 @@ export default class SpecialVisualizations {
|
|||
},
|
||||
],
|
||||
docs: "Shows events that are happening based on a Giggity URL",
|
||||
needsUrls: ["*"],
|
||||
needsUrls: (args) => args[0],
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tagSource: UIEventSource<Record<string, string>>,
|
||||
|
|
Loading…
Reference in a new issue