From e1fd333ca4abfeefa4db12b48b6b74732f655f98 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Wed, 15 Jul 2015 22:52:21 +0100 Subject: [PATCH] fix(ngCsp): allow CSP to be configurable There are two different features in Angular that can break CSP rules: use of `eval` to execute a string as JavaScript and dynamic injection of CSS style rules into the DOM. This change allows us to configure which of these features should be turned off to allow a more fine grained set of CSP rules to be supported. Closes #11933 Closes #8459 --- src/ng/directive/ngCsp.js | 10 ++++++---- src/ng/parse.js | 7 ++++--- test/AngularSpec.js | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/ng/directive/ngCsp.js b/src/ng/directive/ngCsp.js index c99286ed3f33..378333b4a854 100644 --- a/src/ng/directive/ngCsp.js +++ b/src/ng/directive/ngCsp.js @@ -53,10 +53,12 @@ * * * * No declaration means that Angular will assume that you can do inline styles, but it will do - * a runtime check for unsafe-eval. E.g. ``. + * a runtime check for unsafe-eval. E.g. ``. This is backwardly compatible with previous versions + * of Angular. * * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline - * styles and unsafe eval. E.g. ``. + * styles and unsafe eval. E.g. ``. This is backwardly compatible with previous versions + * of Angular. * * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject * inline styles. E.g. ``. @@ -65,8 +67,8 @@ * run eval - no automcatic check for unsafe eval will occur. E.g. `` * * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject - * styles nor use eval, which is the same as an empty: ng-csp, except that no runtime check for - * unsafe eval will occur. E.g.`` + * styles nor use eval, which is the same as an empty: ng-csp. + * E.g.`` * * @example * This example shows how to apply the `ngCsp` directive to the `html` tag. diff --git a/src/ng/parse.js b/src/ng/parse.js index 76efe1772de5..5d0c84aa41bb 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -1701,13 +1701,14 @@ function $ParseProvider() { var cacheDefault = createMap(); var cacheExpensive = createMap(); - this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { + this.$get = ['$filter', function($filter) { + var noUnsafeEval = csp().noUnsafeEval; var $parseOptions = { - csp: $sniffer.csp && $sniffer.csp.noUnsafeEval, + csp: noUnsafeEval, expensiveChecks: false }, $parseOptionsExpensive = { - csp: $sniffer.csp && $sniffer.csp.noUnsafeEval, + csp: noUnsafeEval, expensiveChecks: true }; diff --git a/test/AngularSpec.js b/test/AngularSpec.js index cfff9c283a52..95269f7f38f5 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -799,11 +799,10 @@ describe('angular', function() { var originalFunction; beforeEach(function() { - originalFunction = window.Function; + spyOn(window, 'Function'); }); afterEach(function() { - window.Function = originalFunction; delete csp.rules; }); @@ -814,8 +813,9 @@ describe('angular', function() { it('should return true for noUnsafeEval if eval causes a CSP security policy error', function() { - window.Function = function() { throw new Error('CSP test'); }; + window.Function.andCallFake(function() { throw new Error('CSP test'); }); expect(csp()).toEqual({ noUnsafeEval: true, noInlineStyle: false }); + expect(window.Function).toHaveBeenCalledWith(''); }); @@ -823,31 +823,39 @@ describe('angular', function() { var spy = mockCspElement('ng-csp'); expect(csp()).toEqual({ noUnsafeEval: true, noInlineStyle: true }); expect(spy).toHaveBeenCalledWith('[ng-csp]'); + expect(window.Function).not.toHaveBeenCalled(); }); it('should return true when CSP is enabled manually via [data-ng-csp]', function() { var spy = mockCspElement('data-ng-csp'); expect(csp()).toEqual({ noUnsafeEval: true, noInlineStyle: true }); - expect(document.querySelector).toHaveBeenCalledWith('[data-ng-csp]'); + expect(spy).toHaveBeenCalledWith('[data-ng-csp]'); + expect(window.Function).not.toHaveBeenCalled(); }); it('should return true for noUnsafeEval if it is specified in the `ng-csp` attribute value', function() { var spy = mockCspElement('ng-csp', 'no-unsafe-eval'); expect(csp()).toEqual({ noUnsafeEval: true, noInlineStyle: false }); + expect(spy).toHaveBeenCalledWith('[ng-csp]'); + expect(window.Function).not.toHaveBeenCalled(); }); it('should return true for noInlineStyle if it is specified in the `ng-csp` attribute value', function() { var spy = mockCspElement('ng-csp', 'no-inline-style'); expect(csp()).toEqual({ noUnsafeEval: false, noInlineStyle: true }); + expect(spy).toHaveBeenCalledWith('[ng-csp]'); + expect(window.Function).not.toHaveBeenCalled(); }); it('should return true for all styles if they are all specified in the `ng-csp` attribute value', function() { var spy = mockCspElement('ng-csp', 'no-inline-style;no-unsafe-eval'); expect(csp()).toEqual({ noUnsafeEval: true, noInlineStyle: true }); + expect(spy).toHaveBeenCalledWith('[ng-csp]'); + expect(window.Function).not.toHaveBeenCalled(); }); });