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 crypto from "crypto"
|
||||||
import * as eli from "../src/assets/editor-layer-index.json"
|
import * as eli from "../src/assets/editor-layer-index.json"
|
||||||
import * as eli_global from "../src/assets/global-raster-layers.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 sharp = require("sharp")
|
||||||
const template = readFileSync("theme.html", "utf8")
|
const template = readFileSync("theme.html", "utf8")
|
||||||
|
@ -264,6 +265,7 @@ async function eliUrls(): Promise<string[]> {
|
||||||
|
|
||||||
async function generateCsp(
|
async function generateCsp(
|
||||||
layout: LayoutConfig,
|
layout: LayoutConfig,
|
||||||
|
layoutJson: LayoutConfigJson,
|
||||||
options: {
|
options: {
|
||||||
scriptSrcs: string[]
|
scriptSrcs: string[]
|
||||||
}
|
}
|
||||||
|
@ -275,9 +277,20 @@ async function generateCsp(
|
||||||
Constants.nominatimEndpoint,
|
Constants.nominatimEndpoint,
|
||||||
"https://api.openstreetmap.org",
|
"https://api.openstreetmap.org",
|
||||||
"https://pietervdvn.goatcounter.com",
|
"https://pietervdvn.goatcounter.com",
|
||||||
]
|
].concat(...(await eliUrls()))
|
||||||
.concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls))
|
|
||||||
.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 geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource)
|
||||||
const hosts = new Set<string>()
|
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 connectSrc = Array.from(hosts).sort()
|
||||||
|
|
||||||
const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem))
|
const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem))
|
||||||
|
@ -317,7 +334,7 @@ async function generateCsp(
|
||||||
"default-src": "'self'",
|
"default-src": "'self'",
|
||||||
"child-src": "'self' blob: ",
|
"child-src": "'self' blob: ",
|
||||||
"img-src": "* data:", // maplibre depends on 'data:' to load
|
"img-src": "* data:", // maplibre depends on 'data:' to load
|
||||||
"connect-src": connectSrc.join(" "),
|
"connect-src": onnectSrc.join(" "),
|
||||||
"report-to": "https://report.mapcomplete.org/csp",
|
"report-to": "https://report.mapcomplete.org/csp",
|
||||||
"worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob'
|
"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
|
"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)
|
.update(removeOtherLanguages)
|
||||||
.digest("base64")
|
.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])
|
Locale.language.setData(layout.language[0])
|
||||||
const targetLanguage = layout.language[0]
|
const targetLanguage = layout.language[0]
|
||||||
const ogTitle = Translations.T(layout.title).textFor(targetLanguage).replace(/"/g, '\\"')
|
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(/<!-- THEME-SPECIFIC -->.*<!-- THEME-SPECIFIC-END-->/s, themeSpecific)
|
||||||
.replace(
|
.replace(
|
||||||
/<!-- CSP -->/,
|
/<!-- CSP -->/,
|
||||||
await generateCsp(layout, {
|
await generateCsp(layout, layoutJson, {
|
||||||
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
|
scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -518,7 +541,13 @@ async function main(): Promise<void> {
|
||||||
writeFile("public/" + manifestLocation, manif, err)
|
writeFile("public/" + manifestLocation, manif, err)
|
||||||
|
|
||||||
// Create a landing page for the given theme
|
// 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)
|
writeFile(enc(layout.id) + ".html", landing, err)
|
||||||
await createIndexFor(layout)
|
await createIndexFor(layout)
|
||||||
|
|
|
@ -88,7 +88,7 @@ export interface SpecialVisualization {
|
||||||
readonly funcName: string
|
readonly funcName: string
|
||||||
readonly docs: string | BaseUIElement
|
readonly docs: string | BaseUIElement
|
||||||
readonly example?: string
|
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
|
* 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",
|
docs: "Shows events that are happening based on a Giggity URL",
|
||||||
needsUrls: ["*"],
|
needsUrls: (args) => args[0],
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue