diff --git a/Gruntfile.js b/Gruntfile.js index de93d4b8a2ef..f76ac926dba5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -47,8 +47,7 @@ module.exports = function(grunt) { keepalive: true, middleware: function(connect, options){ return [ - //uncomment to enable CSP - // util.csp(), + util.conditionalCsp(), util.rewrite(), connect.favicon('images/favicon.ico'), connect.static(options.base), @@ -74,6 +73,7 @@ module.exports = function(grunt) { next(); }, + util.conditionalCsp(), connect.favicon('images/favicon.ico'), connect.static(options.base) ]; diff --git a/lib/grunt/utils.js b/lib/grunt/utils.js index b4889c61bc1e..852b993060cb 100644 --- a/lib/grunt/utils.js +++ b/lib/grunt/utils.js @@ -285,11 +285,15 @@ module.exports = { //csp connect middleware - csp: function(){ + conditionalCsp: function(){ return function(req, res, next){ - res.setHeader("X-WebKit-CSP", "default-src 'self';"); - res.setHeader("X-Content-Security-Policy", "default-src 'self'"); - res.setHeader("Content-Security-Policy", "default-src 'self'"); + var CSP = /\.csp\W/; + + if (CSP.test(req.url)) { + res.setHeader("X-WebKit-CSP", "default-src 'self';"); + res.setHeader("X-Content-Security-Policy", "default-src 'self'"); + res.setHeader("Content-Security-Policy", "default-src 'self'"); + } next(); }; }, diff --git a/src/ng/directive/ngCsp.js b/src/ng/directive/ngCsp.js index 691da10d9a87..eb60983e0807 100644 --- a/src/ng/directive/ngCsp.js +++ b/src/ng/directive/ngCsp.js @@ -49,7 +49,125 @@ ... ``` - */ + * @example + // Note: the suffix `.csp` in the example name triggers + // csp mode in our http server! + + +
+
+ + + {{ctrl.counter}} + +
+ +
+ + + {{ctrl.evilError}} + +
+
+
+ + angular.module('cspExample', []) + .controller('MainController', function() { + this.counter = 0; + this.inc = function() { + this.counter++; + }; + this.evil = function() { + // jshint evil:true + try { + eval('1+2'); + } catch (e) { + this.evilError = e.message; + } + }; + }); + + + var util, webdriver; + + var incBtn = element(by.id('inc')); + var counter = element(by.id('counter')); + var evilBtn = element(by.id('evil')); + var evilError = element(by.id('evilError')); + + function getAndClearSevereErrors() { + return browser.manage().logs().get('browser').then(function(browserLog) { + return browserLog.filter(function(logEntry) { + return logEntry.level.value > webdriver.logging.Level.WARNING.value; + }); + }); + } + + function clearErrors() { + getAndClearSevereErrors(); + } + + function expectNoErrors() { + getAndClearSevereErrors().then(function(filteredLog) { + expect(filteredLog.length).toEqual(0); + if (filteredLog.length) { + console.log('browser console errors: ' + util.inspect(filteredLog)); + } + }); + } + + function expectError(regex) { + getAndClearSevereErrors().then(function(filteredLog) { + var found = false; + filteredLog.forEach(function(log) { + if (log.message.match(regex)) { + found = true; + } + }); + if (!found) { + throw new Error('expected an error that matches ' + regex); + } + }); + } + + beforeEach(function() { + util = require('util'); + webdriver = require('protractor/node_modules/selenium-webdriver'); + }); + + // For now, we only test on Chrome, + // as Safari does not load the page with Protractor's injected scripts, + // and Firefox webdriver always disables content security policy (#6358) + if (browser.params.browser !== 'chrome') { + return; + } + + it('should not report errors when the page is loaded', function() { + // clear errors so we are not dependent on previous tests + clearErrors(); + // Need to reload the page as the page is already loaded when + // we come here + browser.driver.getCurrentUrl().then(function(url) { + browser.get(url); + }); + expectNoErrors(); + }); + + it('should evaluate expressions', function() { + expect(counter.getText()).toEqual('0'); + incBtn.click(); + expect(counter.getText()).toEqual('1'); + expectNoErrors(); + }); + + it('should throw and report an error when using "eval"', function() { + evilBtn.click(); + expect(evilError.getText()).toMatch(/Content Security Policy/); + expectError(/Content Security Policy/); + }); + +
+ */ // ngCsp is not implemented as a proper directive any more, because we need it be processed while we // bootstrap the system (before $parse is instantiated), for this reason we just have