From 1fa7a87f866115b6f779cdffcd0d58bcdb5cb84e Mon Sep 17 00:00:00 2001 From: David Taylor Date: Wed, 1 Dec 2021 16:10:40 +0000 Subject: [PATCH] SECURITY: Remove ember-cli specific response from application routes (#15155) Under some conditions, these varied responses could lead to cache poisoning, hence the 'security' label. Previously the Rails application would serve JSON data in place of HTML whenever Ember CLI requested an `application.html.erb`-rendered page. This commit removes that logic, and instead parses the HTML out of the standard response. This means that Rails doesn't need to customize its response for Ember CLI. --- .../javascripts/discourse/ember-cli-build.js | 3 + .../discourse/lib/bootstrap-json/index.js | 58 ++++-- app/assets/javascripts/discourse/package.json | 1 + app/assets/javascripts/yarn.lock | 185 +++++++++++++++++- app/controllers/application_controller.rb | 30 +-- app/controllers/bootstrap_controller.rb | 13 +- app/helpers/application_helper.rb | 6 - app/views/layouts/application.html.erb | 1 - lib/discourse.rb | 5 - spec/components/discourse_spec.rb | 6 - spec/requests/bootstrap_controller_spec.rb | 34 ++++ 11 files changed, 265 insertions(+), 77 deletions(-) diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js index 59c48a1463..d6301bd569 100644 --- a/app/assets/javascripts/discourse/ember-cli-build.js +++ b/app/assets/javascripts/discourse/ember-cli-build.js @@ -25,6 +25,9 @@ module.exports = function (defaults) { // This forces the use of `fast-sourcemap-concat` which works in production. enabled: true, }, + autoImport: { + forbidEval: true, + }, }); // Ember CLI does this by default for the app tree, but for our extra bundles we diff --git a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js index 87342b34d7..197f9975be 100644 --- a/app/assets/javascripts/discourse/lib/bootstrap-json/index.js +++ b/app/assets/javascripts/discourse/lib/bootstrap-json/index.js @@ -6,6 +6,7 @@ const { encode } = require("html-entities"); const cleanBaseURL = require("clean-base-url"); const path = require("path"); const { promises: fs } = require("fs"); +const { JSDOM } = require("jsdom"); // via https://stackoverflow.com/a/6248722/165668 function generateUID() { @@ -168,12 +169,14 @@ function replaceIn(bootstrap, template, id, headers, baseURL) { return template.replace(``, contents); } -async function applyBootstrap(bootstrap, template, response, baseURL) { - // If our initial page added some preload data let's not lose that. - let json = await response.json(); - if (json && json.preloaded) { - bootstrap.preloaded = Object.assign(json.preloaded, bootstrap.preloaded); - } +function extractPreloadJson(html) { + const dom = new JSDOM(html); + return dom.window.document.querySelector("#data-preloaded")?.dataset + ?.preloaded; +} + +async function applyBootstrap(bootstrap, template, response, baseURL, preload) { + bootstrap.preloaded = Object.assign(JSON.parse(preload), bootstrap.preloaded); Object.keys(BUILDERS).forEach((id) => { template = replaceIn(bootstrap, template, id, response.headers, baseURL); @@ -181,23 +184,20 @@ async function applyBootstrap(bootstrap, template, response, baseURL) { return template; } -async function buildFromBootstrap(proxy, baseURL, req, response) { +async function buildFromBootstrap(proxy, baseURL, req, response, preload) { try { const template = await fs.readFile( path.join(process.cwd(), "dist", "index.html"), "utf8" ); - let url = `${proxy}${baseURL}bootstrap.json`; - const queryLoc = req.url.indexOf("?"); - if (queryLoc !== -1) { - url += req.url.substr(queryLoc); - } + let url = new URL(`${proxy}${baseURL}bootstrap.json`); + url.searchParams.append("for_url", req.url); const res = await fetch(url, { headers: req.headers }); const json = await res.json(); - return applyBootstrap(json.bootstrap, template, response, baseURL); + return applyBootstrap(json.bootstrap, template, response, baseURL, preload); } catch (error) { throw new Error( `Could not get ${proxy}${baseURL}bootstrap.json\n\n${error}` @@ -229,7 +229,6 @@ async function handleRequest(proxy, baseURL, req, res) { if (req.method === "GET") { req.headers["X-Discourse-Ember-CLI"] = "true"; - req.headers["X-Discourse-Asset-Path"] = req.path; } const response = await fetch(url, { @@ -251,20 +250,37 @@ async function handleRequest(proxy, baseURL, req, res) { const csp = response.headers.get("content-security-policy"); if (csp) { - const newCSP = csp.replace( - new RegExp(proxy, "g"), - `http://${originalHost}` - ); + const emberCliAdditions = [ + `http://${originalHost}/assets/`, + `http://${originalHost}/ember-cli-live-reload.js`, + `http://${originalHost}/_lr/`, + ]; + const newCSP = csp + .replace(new RegExp(proxy, "g"), `http://${originalHost}`) + .replace( + new RegExp("script-src ", "g"), + `script-src ${emberCliAdditions.join(" ")} ` + ); res.set("content-security-policy", newCSP); } - if (response.headers.get("x-discourse-bootstrap-required") === "true") { - const html = await buildFromBootstrap(proxy, baseURL, req, response); + const isHTML = response.headers["content-type"]?.startsWith("text/html"); + const responseText = await response.text(); + const preload = isHTML ? extractPreloadJson(responseText) : null; + + if (preload) { + const html = await buildFromBootstrap( + proxy, + baseURL, + req, + response, + preload + ); res.set("content-type", "text/html"); res.send(html); } else { res.status(response.status); - res.send(await response.text()); + res.send(responseText); } } diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 59735ab571..32b109253a 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -54,6 +54,7 @@ "eslint": "^7.27.0", "html-entities": "^2.1.0", "js-yaml": "^4.0.0", + "jsdom": "^18.1.1", "loader.js": "^4.7.0", "message-bus-client": "^3.3.0", "messageformat": "0.1.5", diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index 7be414245a..691b5eea85 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -1317,6 +1317,11 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@transloadit/prettier-bytes@0.0.7": version "0.0.7" resolved "https://registry.yarnpkg.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz#cdb5399f445fdd606ed833872fa0cabdbc51686b" @@ -1715,11 +1720,23 @@ acorn@^8.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.1.tgz#fb0026885b9ac9f48bac1e185e4af472971149ff" integrity sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g== +acorn@^8.5.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -4080,7 +4097,7 @@ colors@^1.1.2: resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -4405,6 +4422,11 @@ cssom@^0.4.4: resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + cssom@~0.3.6: version "0.3.8" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" @@ -4443,6 +4465,15 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +data-urls@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.1.tgz#597fc2ae30f8bc4dbcf731fcd1b1954353afc6f8" + integrity sha512-Ds554NeT5Gennfoo9KN50Vh6tpgtvYEwraYjejXnyTpu1C7oXKxdFk75REooENHE8ndTVOJuv+BEs4/J/xcozw== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4450,6 +4481,13 @@ debug@2.6.9, debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0, debug@^2.3. dependencies: ms "2.0.0" +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debug@^3.0.1, debug@^3.1.0, debug@^3.1.1: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -4457,13 +4495,6 @@ debug@^3.0.1, debug@^3.1.0, debug@^3.1.1: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -4483,6 +4514,11 @@ decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -4653,6 +4689,13 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -6391,6 +6434,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -7122,6 +7174,13 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + html-entities@^2.1.0: version "2.3.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488" @@ -7169,6 +7228,15 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy@^1.13.1, http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -7192,6 +7260,14 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + https@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https/-/https-1.0.0.tgz#3c37c7ae1a8eeb966904a2ad1e975a194b7ed3a4" @@ -7209,6 +7285,13 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -7584,7 +7667,7 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-potential-custom-element-name@^1.0.0: +is-potential-custom-element-name@^1.0.0, is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== @@ -7803,6 +7886,39 @@ jsdom@^16.4.0: ws "^7.4.4" xml-name-validator "^3.0.0" +jsdom@^18.1.1: + version "18.1.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-18.1.1.tgz#15ec896f5ab7df9669a62375606f47c8c09551aa" + integrity sha512-NmJQbjQ/gpS/1at/ce3nCx89HbXL/f5OcenBe8wU1Eik0ROhyUc3LtmG3567dEHAGXkN8rmILW/qtCOPxPHQJw== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -10208,7 +10324,7 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -11272,6 +11388,13 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -11688,6 +11811,13 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + walk-sync@^0.2.5: version "0.2.7" resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.2.7.tgz#b49be4ee6867657aeb736978b56a29d10fa39969" @@ -11785,6 +11915,11 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-sources@^1.4.0, webpack-sources@^1.4.1: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" @@ -11843,11 +11978,31 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -11987,6 +12142,11 @@ ws@^7.4.4, ws@~7.4.2: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== +ws@^8.2.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d" + integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw== + x-is-array@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-array/-/x-is-array-0.1.0.tgz#de520171d47b3f416f5587d629b89d26b12dc29d" @@ -12007,6 +12167,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1351d76037..7dd73b3f82 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -119,23 +119,9 @@ class ApplicationController < ActionController::Base class RenderEmpty < StandardError; end class PluginDisabled < StandardError; end - class EmberCLIHijacked < StandardError; end - - def catch_ember_cli_hijack - yield - rescue ActionView::Template::Error => ex - raise ex unless ex.cause.is_a?(EmberCLIHijacked) - send_ember_cli_bootstrap - end rescue_from RenderEmpty do - catch_ember_cli_hijack do - with_resolved_locale { render 'default/empty' } - end - end - - rescue_from EmberCLIHijacked do - send_ember_cli_bootstrap + with_resolved_locale { render 'default/empty' } end rescue_from ArgumentError do |e| @@ -324,21 +310,13 @@ class ApplicationController < ActionController::Base rescue Discourse::InvalidAccess return render plain: message, status: status_code end - catch_ember_cli_hijack do - with_resolved_locale do - error_page_opts[:layout] = opts[:include_ember] ? 'application' : 'no_ember' - render html: build_not_found_page(error_page_opts) - end + with_resolved_locale do + error_page_opts[:layout] = opts[:include_ember] ? 'application' : 'no_ember' + render html: build_not_found_page(error_page_opts) end end end - def send_ember_cli_bootstrap - response.headers['X-Discourse-Bootstrap-Required'] = true - response.headers['Content-Type'] = "application/json" - render json: { preloaded: @preloaded } - end - # If a controller requires a plugin, it will raise an exception if that plugin is # disabled. This allows plugins to be disabled programmatically. def self.requires_plugin(plugin_name) diff --git a/app/controllers/bootstrap_controller.rb b/app/controllers/bootstrap_controller.rb index 7c6c519261..811058d752 100644 --- a/app/controllers/bootstrap_controller.rb +++ b/app/controllers/bootstrap_controller.rb @@ -27,12 +27,21 @@ class BootstrapController < ApplicationController add_style(mobile_view? ? :mobile : :desktop) end add_style(:admin) if staff? + + assets_fake_request = ActionDispatch::Request.new(request.env.dup) + assets_for_url = params[:for_url] + if assets_for_url + path, query = assets_for_url.split("?", 2) + assets_fake_request.env["PATH_INFO"] = path + assets_fake_request.env["QUERY_STRING"] = query + end + Discourse.find_plugin_css_assets( include_official: allow_plugins?, include_unofficial: allow_third_party_plugins?, mobile_view: mobile_view?, desktop_view: !mobile_view?, - request: request + request: assets_fake_request ).each do |file| add_style(file, plugin: true) end @@ -49,7 +58,7 @@ class BootstrapController < ApplicationController plugin_js = Discourse.find_plugin_js_assets( include_official: allow_plugins?, include_unofficial: allow_third_party_plugins?, - request: request + request: assets_fake_request ).map { |f| script_asset_path(f) } bootstrap = { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a5cfbb9aba..c5a9627123 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -647,10 +647,4 @@ module ApplicationHelper current_user ? nil : value end end - - def hijack_if_ember_cli! - if request.headers["HTTP_X_DISCOURSE_EMBER_CLI"] == "true" - raise ApplicationController::EmberCLIHijacked.new - end - end end diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 53a4410a6c..ed984a5bb5 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -131,4 +131,3 @@ <%- end %> -<%- hijack_if_ember_cli! -%> diff --git a/lib/discourse.rb b/lib/discourse.rb index 3e641b4e29..303b94293b 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -338,11 +338,6 @@ module Discourse path = request.fullpath result[:path] = path if path.present? - # When we bootstrap using the JSON method, we want to be able to filter assets on - # the path we're bootstrapping for. - asset_path = request.headers["HTTP_X_DISCOURSE_ASSET_PATH"] - result[:path] = asset_path if asset_path.present? - result end diff --git a/spec/components/discourse_spec.rb b/spec/components/discourse_spec.rb index aa2542e04b..2f47dd0bb5 100644 --- a/spec/components/discourse_spec.rb +++ b/spec/components/discourse_spec.rb @@ -76,12 +76,6 @@ describe Discourse do opts = Discourse.asset_filter_options(:js, req) expect(opts[:path]).to eq("/hello") end - - it "overwrites the path if the asset path is present" do - req = stub(fullpath: "/bootstrap.json", headers: { "HTTP_X_DISCOURSE_ASSET_PATH" => "/hello" }) - opts = Discourse.asset_filter_options(:js, req) - expect(opts[:path]).to eq("/hello") - end end context 'plugins' do diff --git a/spec/requests/bootstrap_controller_spec.rb b/spec/requests/bootstrap_controller_spec.rb index b11e09330f..0eb71ddec2 100644 --- a/spec/requests/bootstrap_controller_spec.rb +++ b/spec/requests/bootstrap_controller_spec.rb @@ -93,4 +93,38 @@ describe BootstrapController do expect(bootstrap['authentication_data']).to eq(cookie_data) end end + + context 'with a plugin asset filter' do + let :plugin do + plugin = Plugin::Instance.new + plugin.path = "#{Rails.root}/spec/fixtures/plugins/my_plugin/plugin.rb" + plugin.register_asset_filter do |type, request| + next true if request.path == "/mypluginroute" + false + end + plugin + end + + before do + Discourse.plugins << plugin + plugin.activate! + end + + after do + Discourse.plugins.delete plugin + end + + it "filters assets using the given path" do + get "/bootstrap.json" + expect(response.status).to eq(200) + plugin_assets = response.parsed_body.dig("bootstrap", "plugin_js") + expect(plugin_assets).not_to include(a_string_matching "my_plugin") + + get "/bootstrap.json?for_url=/mypluginroute" + expect(response.status).to eq(200) + plugin_assets = response.parsed_body.dig("bootstrap", "plugin_js") + expect(plugin_assets).to include(a_string_matching "my_plugin") + end + + end end