diff --git a/package.json b/package.json index 30c4bf8..f2811fe 100644 --- a/package.json +++ b/package.json @@ -184,6 +184,7 @@ "less-loader": "^4.1.0", "mini-css-extract-plugin": "^0.4.1", "mocha": "^5.2.0", + "mock-require": "^3.0.3", "optimize-css-assets-webpack-plugin": "^5.0.0", "script-loader": "^0.7.2", "string-loader": "^0.0.1", diff --git a/test/csp.js b/test/csp.js new file mode 100644 index 0000000..a6de68a --- /dev/null +++ b/test/csp.js @@ -0,0 +1,124 @@ +/* eslint-env node, mocha */ +'use strict' + +const assert = require('assert') +const crypto = require('crypto') +const fs = require('fs') +const path = require('path') +const mock = require('mock-require') + +describe('Content security policies', function () { + let defaultConfig, csp + + before(function () { + csp = require('../lib/csp') + }) + + beforeEach(function () { + // Reset config to make sure we don't influence other tests + defaultConfig = { + csp: { + enable: true, + directives: { + }, + addDefaults: true, + addDisqus: true, + addGoogleAnalytics: true, + upgradeInsecureRequests: 'auto', + reportURI: undefined + }, + useCDN: true + } + }) + + afterEach(function () { + mock.stop('../lib/config') + csp = mock.reRequire('../lib/csp') + }) + + after(function () { + mock.stopAll() + csp = mock.reRequire('../lib/csp') + }) + + // beginnging Tests + it('Disable CDN', function () { + let testconfig = defaultConfig + testconfig.useCDN = false + mock('../lib/config', testconfig) + csp = mock.reRequire('../lib/csp') + + assert(!csp.computeDirectives().scriptSrc.includes('https://cdnjs.cloudflare.com')) + assert(!csp.computeDirectives().scriptSrc.includes('https://cdn.mathjax.org')) + assert(!csp.computeDirectives().styleSrc.includes('https://cdnjs.cloudflare.com')) + assert(!csp.computeDirectives().styleSrc.includes('https://fonts.googleapis.com')) + assert(!csp.computeDirectives().fontSrc.includes('https://cdnjs.cloudflare.com')) + assert(!csp.computeDirectives().fontSrc.includes('https://fonts.gstatic.com')) + }) + + it('Disable Google Analytics', function () { + let testconfig = defaultConfig + testconfig.csp.addGoogleAnalytics = false + mock('../lib/config', testconfig) + csp = mock.reRequire('../lib/csp') + + assert(!csp.computeDirectives().scriptSrc.includes('https://www.google-analytics.com')) + }) + + it('Disable Disqus', function () { + let testconfig = defaultConfig + testconfig.csp.addDisqus = false + mock('../lib/config', testconfig) + csp = mock.reRequire('../lib/csp') + + assert(!csp.computeDirectives().scriptSrc.includes('https://disqus.com')) + assert(!csp.computeDirectives().scriptSrc.includes('https://*.disqus.com')) + assert(!csp.computeDirectives().scriptSrc.includes('https://*.disquscdn.com')) + assert(!csp.computeDirectives().styleSrc.includes('https://*.disquscdn.com')) + assert(!csp.computeDirectives().fontSrc.includes('https://*.disquscdn.com')) + }) + + it('Set ReportURI', function () { + let testconfig = defaultConfig + testconfig.csp.reportURI = 'https://example.com/reportURI' + mock('../lib/config', testconfig) + csp = mock.reRequire('../lib/csp') + + assert.strictEqual(csp.computeDirectives().reportUri, 'https://example.com/reportURI') + }) + + it('Set own directives', function () { + let testconfig = defaultConfig + mock('../lib/config', defaultConfig) + csp = mock.reRequire('../lib/csp') + const unextendedCSP = csp.computeDirectives() + testconfig.csp.directives = { + defaultSrc: ['https://default.example.com'], + scriptSrc: ['https://script.example.com'], + imgSrc: ['https://img.example.com'], + styleSrc: ['https://style.example.com'], + fontSrc: ['https://font.example.com'], + objectSrc: ['https://object.example.com'], + mediaSrc: ['https://media.example.com'], + childSrc: ['https://child.example.com'], + connectSrc: ['https://connect.example.com'] + } + mock('../lib/config', testconfig) + csp = mock.reRequire('../lib/csp') + + const variations = ['default', 'script', 'img', 'style', 'font', 'object', 'media', 'child', 'connect'] + + for (let i = 0; i < variations.length; i++) { + assert.strictEqual(csp.computeDirectives()[variations[i] + 'Src'].toString(), ['https://' + variations[i] + '.example.com'].concat(unextendedCSP[variations[i] + 'Src']).toString()) + } + }) + + /* + * This test reminds us to update the CSP hash for the speaker notes + */ + it('Unchanged hash for reveal.js speaker notes plugin', function () { + const hash = crypto.createHash('sha1') + hash.update(fs.readFileSync(path.resolve(__dirname, '../node_modules/reveal.js/plugin/notes/notes.html'), 'utf8'), 'utf8') + assert.strictEqual(hash.digest('hex'), '471f3826880fac884a4a14faabc492bc854ae994') + }) +})