Skip to content

Commit b9479ee

Browse files
committed
chore(ngCsp): add e2e tests
Also changes `connect:devserver` and `connect:testserver` to conditionally serve files with csp headers when the path contains `.csp` somewhere. Closes angular#9136 Closes angular#9059
1 parent 769a00d commit b9479ee

File tree

3 files changed

+129
-7
lines changed

3 files changed

+129
-7
lines changed

Gruntfile.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ module.exports = function(grunt) {
4747
keepalive: true,
4848
middleware: function(connect, options){
4949
return [
50-
//uncomment to enable CSP
51-
// util.csp(),
50+
util.conditionalCsp(),
5251
util.rewrite(),
5352
connect.favicon('images/favicon.ico'),
5453
connect.static(options.base),
@@ -74,6 +73,7 @@ module.exports = function(grunt) {
7473

7574
next();
7675
},
76+
util.conditionalCsp(),
7777
connect.favicon('images/favicon.ico'),
7878
connect.static(options.base)
7979
];

lib/grunt/utils.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,15 @@ module.exports = {
285285

286286

287287
//csp connect middleware
288-
csp: function(){
288+
conditionalCsp: function(){
289289
return function(req, res, next){
290-
res.setHeader("X-WebKit-CSP", "default-src 'self';");
291-
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
292-
res.setHeader("Content-Security-Policy", "default-src 'self'");
290+
var CSP = /\.csp\W/;
291+
292+
if (CSP.test(req.url)) {
293+
res.setHeader("X-WebKit-CSP", "default-src 'self';");
294+
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
295+
res.setHeader("Content-Security-Policy", "default-src 'self'");
296+
}
293297
next();
294298
};
295299
},

src/ng/directive/ngCsp.js

+119-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,125 @@
4949
...
5050
</html>
5151
```
52-
*/
52+
* @example
53+
// Note: the suffix `.csp` in the example name triggers
54+
// csp mode in our http server!
55+
<example name="example.csp" module="cspExample" ng-csp="true">
56+
<file name="index.html">
57+
<div ng-controller="MainController as ctrl">
58+
<div>
59+
<button ng-click="ctrl.inc()" id="inc">Increment</button>
60+
<span id="counter">
61+
{{ctrl.counter}}
62+
</span>
63+
</div>
64+
65+
<div>
66+
<button ng-click="ctrl.evil()" id="evil">Evil</button>
67+
<span id="evilError">
68+
{{ctrl.evilError}}
69+
</span>
70+
</div>
71+
</div>
72+
</file>
73+
<file name="script.js">
74+
angular.module('cspExample', [])
75+
.controller('MainController', function() {
76+
this.counter = 0;
77+
this.inc = function() {
78+
this.counter++;
79+
};
80+
this.evil = function() {
81+
// jshint evil:true
82+
try {
83+
eval('1+2');
84+
} catch (e) {
85+
this.evilError = e.message;
86+
}
87+
};
88+
});
89+
</file>
90+
<file name="protractor.js" type="protractor">
91+
var util, webdriver;
92+
93+
var incBtn = element(by.id('inc'));
94+
var counter = element(by.id('counter'));
95+
var evilBtn = element(by.id('evil'));
96+
var evilError = element(by.id('evilError'));
97+
98+
function getAndClearSevereErrors() {
99+
return browser.manage().logs().get('browser').then(function(browserLog) {
100+
return browserLog.filter(function(logEntry) {
101+
return logEntry.level.value > webdriver.logging.Level.WARNING.value;
102+
});
103+
});
104+
}
105+
106+
function clearErrors() {
107+
getAndClearSevereErrors();
108+
}
109+
110+
function expectNoErrors() {
111+
getAndClearSevereErrors().then(function(filteredLog) {
112+
expect(filteredLog.length).toEqual(0);
113+
if (filteredLog.length) {
114+
console.log('browser console errors: ' + util.inspect(filteredLog));
115+
}
116+
});
117+
}
118+
119+
function expectError(regex) {
120+
getAndClearSevereErrors().then(function(filteredLog) {
121+
var found = false;
122+
filteredLog.forEach(function(log) {
123+
if (log.message.match(regex)) {
124+
found = true;
125+
}
126+
});
127+
if (!found) {
128+
throw new Error('expected an error that matches ' + regex);
129+
}
130+
});
131+
}
132+
133+
beforeEach(function() {
134+
util = require('util');
135+
webdriver = require('protractor/node_modules/selenium-webdriver');
136+
});
137+
138+
// For now, we only test on Chrome,
139+
// as Safari does not load the page with Protractor's injected scripts,
140+
// and Firefox webdriver always disables content security policy (#6358)
141+
if (browser.params.browser !== 'chrome') {
142+
return;
143+
}
144+
145+
it('should not report errors when the page is loaded', function() {
146+
// clear errors so we are not dependent on previous tests
147+
clearErrors();
148+
// Need to reload the page as the page is already loaded when
149+
// we come here
150+
browser.driver.getCurrentUrl().then(function(url) {
151+
browser.get(url);
152+
});
153+
expectNoErrors();
154+
});
155+
156+
it('should evaluate expressions', function() {
157+
expect(counter.getText()).toEqual('0');
158+
incBtn.click();
159+
expect(counter.getText()).toEqual('1');
160+
expectNoErrors();
161+
});
162+
163+
it('should throw and report an error when using "eval"', function() {
164+
evilBtn.click();
165+
expect(evilError.getText()).toMatch(/Content Security Policy/);
166+
expectError(/Content Security Policy/);
167+
});
168+
</file>
169+
</example>
170+
*/
53171

54172
// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
55173
// bootstrap the system (before $parse is instantiated), for this reason we just have

0 commit comments

Comments
 (0)