Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 08f376f

Browse files
committed
fix(csp): fix csp auto-detection and stylesheet injection
When we refactored , we broke the csp mode because the previous implementation relied on the fact that it was ok to lazy initialize the .csp property, this is not the case any more. Besides, we need to know about csp mode during bootstrap and avoid injecting the stylesheet when csp is active, so I refactored the code to fix both issues. PR #4411 will follow up on this commit and add more improvements. Closes #917 Closes #2963 Closes #4394 Closes #4444 BREAKING CHANGE: triggering ngCsp directive via `ng:csp` attribute is not supported any more. Please use data-ng-csp instead.
1 parent 1443805 commit 08f376f

File tree

8 files changed

+64
-40
lines changed

8 files changed

+64
-40
lines changed

lib/grunt/utils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ module.exports = {
9191
.replace(/\\/g, '\\\\')
9292
.replace(/'/g, "\\'")
9393
.replace(/\r?\n/g, '\\n');
94-
return "angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
94+
return "!angular.$$csp() && angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
9595
}
9696
},
9797

src/Angular.js

+7
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,13 @@ function equals(o1, o2) {
760760
}
761761

762762

763+
function csp() {
764+
return (document.securityPolicy && document.securityPolicy.isActive) ||
765+
(document.querySelector &&
766+
!!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]')));
767+
}
768+
769+
763770
function concat(array1, array2, index) {
764771
return array1.concat(slice.call(array2, index));
765772
}

src/AngularPublic.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,13 @@ function publishExternalAPI(angular){
4444
'isNumber': isNumber,
4545
'isElement': isElement,
4646
'isArray': isArray,
47-
'$$minErr': minErr,
4847
'version': version,
4948
'isDate': isDate,
5049
'lowercase': lowercase,
5150
'uppercase': uppercase,
52-
'callbacks': {counter: 0}
51+
'callbacks': {counter: 0},
52+
'$$minErr': minErr,
53+
'$$csp': csp
5354
});
5455

5556
angularModule = setupModuleLoader(window);
@@ -77,7 +78,6 @@ function publishExternalAPI(angular){
7778
ngClass: ngClassDirective,
7879
ngClassEven: ngClassEvenDirective,
7980
ngClassOdd: ngClassOddDirective,
80-
ngCsp: ngCspDirective,
8181
ngCloak: ngCloakDirective,
8282
ngController: ngControllerDirective,
8383
ngForm: ngFormDirective,

src/ng/directive/ngCsp.js

+10-14
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@
33
/**
44
* @ngdoc directive
55
* @name ng.directive:ngCsp
6-
* @priority 1000
76
*
87
* @element html
98
* @description
109
* Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support.
11-
*
10+
*
1211
* This is necessary when developing things like Google Chrome Extensions.
13-
*
12+
*
1413
* CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things).
1514
* For us to be compatible, we just need to implement the "getterFn" in $parse without violating
1615
* any of these restrictions.
17-
*
16+
*
1817
* AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp`
1918
* directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
2019
* evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will
2120
* be raised.
22-
*
21+
*
2322
* In order to use this feature put the `ngCsp` directive on the root element of the application.
24-
*
23+
*
24+
* *Note: This directive is only available in the ng-csp and data-ng-csp attribute form.*
25+
*
2526
* @example
2627
* This example shows how to apply the `ngCsp` directive to the `html` tag.
2728
<pre>
@@ -33,11 +34,6 @@
3334
</pre>
3435
*/
3536

36-
var ngCspDirective = ['$sniffer', function($sniffer) {
37-
return {
38-
priority: 1000,
39-
compile: function() {
40-
$sniffer.csp = true;
41-
}
42-
};
43-
}];
37+
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap
38+
// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute
39+
// anywhere in the current doc

src/ng/sniffer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function $SnifferProvider() {
7676

7777
return eventSupport[event];
7878
},
79-
csp: document.securityPolicy ? document.securityPolicy.isActive : false,
79+
csp: csp(),
8080
vendorPrefix: vendorPrefix,
8181
transitions : transitions,
8282
animations : animations

test/AngularSpec.js

+40
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,46 @@ describe('angular', function() {
348348
});
349349

350350

351+
describe('csp', function() {
352+
var originalSecurityPolicy;
353+
354+
beforeEach(function() {
355+
originalSecurityPolicy = document.securityPolicy;
356+
});
357+
358+
afterEach(function() {
359+
document.securityPolicy = originalSecurityPolicy;
360+
});
361+
362+
363+
it('should return the false when CSP is not enabled (the default)', function() {
364+
expect(csp()).toBe(false);
365+
});
366+
367+
368+
it('should return true if CSP is autodetected via CSP v1.1 securityPolicy.isActive property', function() {
369+
document.securityPolicy = {isActive: true};
370+
expect(csp()).toBe(true);
371+
});
372+
373+
it('should return the true when CSP is enabled manually via [ng-csp]', function() {
374+
spyOn(document, 'querySelector').andCallFake(function(selector) {
375+
if (selector == '[ng-csp]') return {};
376+
});
377+
expect(csp()).toBe(true);
378+
});
379+
380+
381+
it('should return the true when CSP is enabled manually via [data-ng-csp]', function() {
382+
spyOn(document, 'querySelector').andCallFake(function(selector) {
383+
if (selector == '[data-ng-csp]') return {};
384+
});
385+
expect(csp()).toBe(true);
386+
expect(document.querySelector).toHaveBeenCalledWith('[data-ng-csp]');
387+
});
388+
});
389+
390+
351391
describe('parseKeyValue', function() {
352392
it('should parse a string into key-value pairs', function() {
353393
expect(parseKeyValue('')).toEqual({});

test/ng/directive/ngCspSpec.js

-10
This file was deleted.

test/ng/snifferSpec.js

+2-11
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,12 @@ describe('$sniffer', function() {
8585

8686

8787
describe('csp', function() {
88-
it('should be false if document.securityPolicy.isActive not available', function() {
88+
it('should be false by default', function() {
8989
expect(sniffer({}).csp).toBe(false);
9090
});
91-
92-
93-
it('should use document.securityPolicy.isActive if available', function() {
94-
var createDocumentWithCSP = function(csp) {
95-
return {securityPolicy: {isActive: csp}};
96-
};
97-
98-
expect(sniffer({}, createDocumentWithCSP(false)).csp).toBe(false);
99-
expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true);
100-
});
10191
});
10292

93+
10394
describe('vendorPrefix', function() {
10495

10596
it('should return the correct vendor prefix based on the browser', function() {

0 commit comments

Comments
 (0)