From 121ce4911055235c0117070560d0135f3b930021 Mon Sep 17 00:00:00 2001 From: Chirayu Krishnappa Date: Tue, 12 Nov 2013 14:07:16 -0800 Subject: [PATCH] feat($parse): config option blockPrivateSymbols=false --- src/ng/parse.js | 71 +++++-- test/ng/parseSpec.js | 464 +++++++++++++++++++++++-------------------- 2 files changed, 297 insertions(+), 238 deletions(-) diff --git a/src/ng/parse.js b/src/ng/parse.js index eeb60c4e4e1f..65b685c6aa7a 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -18,7 +18,9 @@ var promiseWarning; // // We want to prevent this type of access. For the sake of performance, during the lexing phase we // disallow any "dotted" access to any member named "constructor" or to any member whose name begins -// or ends with an underscore. The latter allows one to exclude the private / JavaScript only API +// or ends with an underscore (this is not enabled by default. See +// {@link ng.$parseProvider#blockPrivateSymbols blockPrivateSymbols}). +// The latter allows one to exclude the private / JavaScript only API // available on the scope and controllers from the context of an Angular expression. // // For reflective calls (a[b]), we check that the value of the lookup is not the Function @@ -38,16 +40,15 @@ var promiseWarning; // In general, it is not possible to access a Window object from an angular expression unless a // window or some DOM object that has a reference to window is published onto a Scope. -function ensureSafeMemberName(name, fullExpression, allowConstructor) { - if (typeof name !== 'string' && toString.apply(name) !== "[object String]") { - return name; - } +function ensureSafeMemberName(name, fullExpression, blockPrivateSymbols, allowConstructor) { if (name === "constructor" && !allowConstructor) { throw $parseMinErr('isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); } - if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { + if (blockPrivateSymbols && + (typeof name === 'string' || toString.apply(name) === "[object String]") && + (name.charAt(0) === '_' || name.charAt(name.length-1) === '_')) { throw $parseMinErr('isecprv', 'Referencing private fields in Angular expressions is disallowed! Expression: {0}', fullExpression); @@ -728,7 +729,8 @@ Parser.prototype = { }, objectIndex: function(obj) { - var parser = this; + var parser = this, + blockPrivateSymbols = parser.options.blockPrivateSymbols; var indexFn = this.expression(); this.consume(']'); @@ -738,7 +740,7 @@ Parser.prototype = { // In the getter, we will not block looking up "constructor" by name in order to support user defined // constructors. However, if value looked up is the Function constructor, we will still block it in the // ensureSafeObject call right after we look up o[i] (a few lines below.) - i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */), + i = ensureSafeMemberName(indexFn(self, locals), parser.text, blockPrivateSymbols, true /* allowConstructor */), v, p; if (!o) return undefined; @@ -754,7 +756,7 @@ Parser.prototype = { return v; }, { assign: function(self, value, locals) { - var key = ensureSafeMemberName(indexFn(self, locals), parser.text); + var key = ensureSafeMemberName(indexFn(self, locals), parser.text, blockPrivateSymbols); // prevent overwriting of Function.constructor which would break ensureSafeObject check var safe = ensureSafeObject(obj(self, locals), parser.text); return safe[key] = value; @@ -860,10 +862,11 @@ Parser.prototype = { function setter(obj, path, setValue, fullExp, options) { //needed? options = options || {}; + var blockPrivateSymbols = options.blockPrivateSymbols; var element = path.split('.'), key; for (var i = 0; element.length > 1; i++) { - key = ensureSafeMemberName(element.shift(), fullExp); + key = ensureSafeMemberName(element.shift(), fullExp, blockPrivateSymbols); var propertyObj = obj[key]; if (!propertyObj) { propertyObj = {}; @@ -883,7 +886,7 @@ function setter(obj, path, setValue, fullExp, options) { obj = obj.$$v; } } - key = ensureSafeMemberName(element.shift(), fullExp); + key = ensureSafeMemberName(element.shift(), fullExp, blockPrivateSymbols); obj[key] = setValue; return setValue; } @@ -896,11 +899,12 @@ var getterFnCache = {}; * - http://jsperf.com/path-evaluation-simplified/7 */ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { - ensureSafeMemberName(key0, fullExp); - ensureSafeMemberName(key1, fullExp); - ensureSafeMemberName(key2, fullExp); - ensureSafeMemberName(key3, fullExp); - ensureSafeMemberName(key4, fullExp); + var blockPrivateSymbols = options.blockPrivateSymbols; + ensureSafeMemberName(key0, fullExp, blockPrivateSymbols); + ensureSafeMemberName(key1, fullExp, blockPrivateSymbols); + ensureSafeMemberName(key2, fullExp, blockPrivateSymbols); + ensureSafeMemberName(key3, fullExp, blockPrivateSymbols); + ensureSafeMemberName(key4, fullExp, blockPrivateSymbols); return !options.unwrapPromises ? function cspSafeGetter(scope, locals) { @@ -1001,6 +1005,7 @@ function getterFn(path, options, fullExp) { var pathKeys = path.split('.'), pathKeysLength = pathKeys.length, + blockPrivateSymbols = options.blockPrivateSymbols, fn; if (options.csp) { @@ -1023,7 +1028,7 @@ function getterFn(path, options, fullExp) { } else { var code = 'var l, fn, p;\n'; forEach(pathKeys, function(key, index) { - ensureSafeMemberName(key, fullExp); + ensureSafeMemberName(key, fullExp, blockPrivateSymbols); code += 'if(s === null || s === undefined) return s;\n' + 'l=s;\n' + 's='+ (index @@ -1120,7 +1125,8 @@ function $ParseProvider() { var $parseOptions = { csp: false, unwrapPromises: false, - logPromiseWarnings: true + logPromiseWarnings: true, + blockPrivateSymbols: false }; @@ -1206,6 +1212,35 @@ function $ParseProvider() { } }; + /** + * @ngdoc method + * @name ng.$parseProvider#blockPrivateSymbols + * @methodOf ng.$parseProvider + * @description + * + * If set to true (*default: false*), $parse will disallow access to + * properties whose names begin or end with an underscore character. + * + * This is good to enable, particularly if one is using the + * "controller as" pattern or if you would like to prevent access to + * some properties that are exposed on the scope chain but should not + * be available from template expressions.  In such a scheme, one + * would using a coding convention to ensure that any property that + * should not be available from a template expression begins or ends + * with an underscore character. + * + * @param {boolean=} value New value. + * @returns {boolean|self} Returns the current setting when used as + * getter and self if used as setter. + */ + this.blockPrivateSymbols = function(value) { + if (isDefined(value)) { + $parseOptions.blockPrivateSymbols = !!value; + return this; + } else { + return $parseOptions.blockPrivateSymbols; + } + }; this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { $parseOptions.csp = $sniffer.csp; diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index c72b7e818749..49197c400a7f 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -590,226 +590,6 @@ describe('parser', function() { expect(scope.$eval('bool.toString()')).toBe('false'); }); - describe('sandboxing', function() { - describe('private members', function() { - it('should NOT allow access to private members', function() { - forEach(['_name', 'name_', '_', '_name_'], function(name) { - function _testExpression(expression) { - scope.a = {b: name}; - scope[name] = {a: scope.a}; - scope.piece_1 = "XX" + name.charAt(0) + "XX"; - scope.piece_2 = "XX" + name.substr(1) + "XX"; - expect(function() { - scope.$eval(expression); - }).toThrowMinErr( - '$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' + - 'Expression: ' + expression); - } - - function testExpression(expression) { - if (expression.indexOf('"NAME"') != -1) { - var concatExpr = 'piece_1.substr(2, 1) + piece_2.substr(2, LEN)'.replace('LEN', name.length-1); - _testExpression(expression.replace(/"NAME"/g, concatExpr)); - _testExpression(expression.replace(/"NAME"/g, '(' + concatExpr + ')')); - } - _testExpression(expression.replace(/NAME/g, name)); - } - - // Not all of these are exploitable. The tests ensure that the contract is honored - // without caring about the implementation or exploitability. - testExpression('NAME'); testExpression('NAME = 1'); - testExpression('(NAME)'); testExpression('(NAME) = 1'); - testExpression('a.NAME'); testExpression('a.NAME = 1'); - testExpression('NAME.b'); testExpression('NAME.b = 1'); - testExpression('a.NAME.b'); testExpression('a.NAME.b = 1'); - testExpression('NAME()'); testExpression('NAME() = 1'); - testExpression('(NAME)()'); testExpression('(NAME = 1)()'); - testExpression('(NAME).foo()'); testExpression('(NAME = 1).foo()'); - testExpression('a.NAME()'); testExpression('a.NAME() = 1'); - testExpression('a.NAME.foo()'); testExpression('a.NAME.foo()'); - testExpression('foo(NAME)'); testExpression('foo(NAME = 1)'); - testExpression('foo(a.NAME)'); testExpression('foo(a.NAME = 1)'); - testExpression('foo(1, a.NAME)'); testExpression('foo(1, a.NAME = 1)'); - testExpression('foo(a["NAME"])'); testExpression('foo(a["NAME"] = 1)'); - testExpression('foo(1, a["NAME"])'); testExpression('foo(1, a["NAME"] = 1)'); - testExpression('foo(b = a["NAME"])'); testExpression('foo(b = (a["NAME"] = 1))'); - testExpression('a["NAME"]'); testExpression('a["NAME"] = 1'); - testExpression('a["NAME"]()'); - testExpression('a["NAME"].foo()'); - testExpression('a.b["NAME"]'); testExpression('a.b["NAME"] = 1'); - testExpression('a["b"]["NAME"]'); testExpression('a["b"]["NAME"] = 1'); - }); - }); - }); - - describe('Function constructor', function() { - it('should NOT allow access to Function constructor in getter', function() { - expect(function() { - scope.$eval('{}.toString.constructor'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString.constructor'); - - expect(function() { - scope.$eval('{}.toString.constructor("alert(1)")'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString.constructor("alert(1)")'); - - expect(function() { - scope.$eval('[].toString.constructor.foo'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: [].toString.constructor.foo'); - - expect(function() { - scope.$eval('{}.toString["constructor"]'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: {}.toString["constructor"]'); - expect(function() { - scope.$eval('{}["toString"]["constructor"]'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: {}["toString"]["constructor"]'); - - scope.a = []; - expect(function() { - scope.$eval('a.toString.constructor', scope); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: a.toString.constructor'); - expect(function() { - scope.$eval('a.toString["constructor"]', scope); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: a.toString["constructor"]'); - }); - - it('should NOT allow access to Function constructor in setter', function() { - expect(function() { - scope.$eval('{}.toString.constructor = 1'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString.constructor = 1'); - - expect(function() { - scope.$eval('{}.toString.constructor.a = 1'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString.constructor.a = 1'); - - expect(function() { - scope.$eval('{}.toString["constructor"]["constructor"] = 1'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString["constructor"]["constructor"] = 1'); - - - scope.key1 = "const"; - scope.key2 = "ructor"; - expect(function() { - scope.$eval('{}.toString[key1 + key2].foo'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: {}.toString[key1 + key2].foo'); - - expect(function() { - scope.$eval('{}.toString[key1 + key2] = 1'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString[key1 + key2] = 1'); - - expect(function() { - scope.$eval('{}.toString[key1 + key2].foo = 1'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: {}.toString[key1 + key2].foo = 1'); - - expect(function() { - scope.$eval('{}.toString["constructor"]["a"] = 1'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: {}.toString["constructor"]["a"] = 1'); - - scope.a = []; - expect(function() { - scope.$eval('a.toString.constructor = 1', scope); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: a.toString.constructor = 1'); - }); - - - it('should NOT allow access to Function constructor that has been aliased', function() { - scope.foo = { "bar": Function }; - expect(function() { - scope.$eval('foo["bar"]'); - }).toThrowMinErr( - '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + - 'Expression: foo["bar"]'); - - }); - - - it('should NOT allow access to Function constructor in getter', function() { - expect(function() { - scope.$eval('{}.toString.constructor'); - }).toThrowMinErr( - '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + - 'Expression: {}.toString.constructor'); - }); - }); - - - describe('Window and $element/node', function() { - it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) { - scope.wrap = {w: $window, d: $document}; - - expect(function() { - scope.$eval('wrap["w"]', scope); - }).toThrowMinErr( - '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + - 'disallowed! Expression: wrap["w"]'); - expect(function() { - scope.$eval('wrap["d"]', scope); - }).toThrowMinErr( - '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + - 'disallowed! Expression: wrap["d"]'); - })); - - it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) { - scope.getWin = valueFn($window); - scope.getDoc = valueFn($document); - - expect(function() { - scope.$eval('getWin()', scope); - }).toThrowMinErr( - '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + - 'disallowed! Expression: getWin()'); - expect(function() { - scope.$eval('getDoc()', scope); - }).toThrowMinErr( - '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + - 'disallowed! Expression: getDoc()'); - })); - - it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) { - scope.a = {b: { win: $window, doc: $document }}; - expect(function() { - scope.$eval('a.b.win.alert(1)', scope); - }).toThrowMinErr( - '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + - 'disallowed! Expression: a.b.win.alert(1)'); - expect(function() { - scope.$eval('a.b.doc.on("click")', scope); - }).toThrowMinErr( - '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + - 'disallowed! Expression: a.b.doc.on("click")'); - })); - }); - }); - describe('overriding constructor', function() { it('should evaluate grouped expressions', function() { scope.foo = function foo() { @@ -1033,6 +813,250 @@ describe('parser', function() { }); + forEach([true, false], function(cspEnabled) { + var scope; + describe('sandboxing: cspEnabled: ' + cspEnabled, function() { + function configureEnvironment(cspEnabled, blockPrivateSymbols) { + beforeEach(function() { + module(function($parseProvider) { + $parseProvider.blockPrivateSymbols(blockPrivateSymbols); + }); + inject(function($sniffer, $rootScope) { + $sniffer.csp = cspEnabled; + scope = $rootScope; + }); + }); + } + forEach([false, true], function(blockPrivateSymbols) { + describe('private members: blockPrivateSymbols: ' + blockPrivateSymbols, function() { + configureEnvironment(cspEnabled, blockPrivateSymbols); + it('should NOT allow access to private members', function() { + forEach(['_name', 'name_', '_', '_name_'], function(name) { + function _testExpression(expression) { + scope.a = {b: name}; + scope[name] = {a: scope.a}; + scope.piece_1 = "XX" + name.charAt(0) + "XX"; + scope.piece_2 = "XX" + name.substr(1) + "XX"; + var expected = expect(function() { + scope.$eval(expression); + }); + if (!blockPrivateSymbols) { + expected = expected.not; + } + expected.toThrowMinErr( + '$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' + + 'Expression: ' + expression); + } + + function testExpression(expression) { + if (expression.indexOf('"NAME"') != -1) { + var concatExpr = 'piece_1.substr(2, 1) + piece_2.substr(2, LEN)'.replace('LEN', name.length-1); + _testExpression(expression.replace(/"NAME"/g, concatExpr)); + _testExpression(expression.replace(/"NAME"/g, '(' + concatExpr + ')')); + } + _testExpression(expression.replace(/NAME/g, name)); + } + + // Not all of these are exploitable. The tests ensure that the contract is honored + // without caring about the implementation or exploitability. + testExpression('NAME'); testExpression('NAME = 1'); + testExpression('(NAME)'); testExpression('(NAME) = 1'); + testExpression('a.NAME'); testExpression('a.NAME = 1'); + testExpression('NAME.b'); testExpression('NAME.b = 1'); + testExpression('a.NAME.b'); testExpression('a.NAME.b = 1'); + testExpression('NAME()'); testExpression('NAME() = 1'); + testExpression('(NAME)()'); testExpression('(NAME = 1)()'); + testExpression('(NAME).foo()'); testExpression('(NAME = 1).foo()'); + testExpression('a.NAME()'); testExpression('a.NAME() = 1'); + testExpression('a.NAME.foo()'); testExpression('a.NAME.foo()'); + testExpression('foo(NAME)'); testExpression('foo(NAME = 1)'); + testExpression('foo(a.NAME)'); testExpression('foo(a.NAME = 1)'); + testExpression('foo(1, a.NAME)'); testExpression('foo(1, a.NAME = 1)'); + testExpression('foo(a["NAME"])'); testExpression('foo(a["NAME"] = 1)'); + testExpression('foo(1, a["NAME"])'); testExpression('foo(1, a["NAME"] = 1)'); + testExpression('foo(b = a["NAME"])'); testExpression('foo(b = (a["NAME"] = 1))'); + testExpression('a["NAME"]'); testExpression('a["NAME"] = 1'); + testExpression('a["NAME"]()'); + testExpression('a["NAME"].foo()'); + testExpression('a.b["NAME"]'); testExpression('a.b["NAME"] = 1'); + testExpression('a["b"]["NAME"]'); testExpression('a["b"]["NAME"] = 1'); + }); + }); + }); + }); + + describe('Function constructor', function() { + configureEnvironment(cspEnabled, false); + it('should NOT allow access to Function constructor in getter', function() { + expect(function() { + scope.$eval('{}.toString.constructor'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString.constructor'); + + expect(function() { + scope.$eval('{}.toString.constructor("alert(1)")'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString.constructor("alert(1)")'); + + expect(function() { + scope.$eval('[].toString.constructor.foo'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: [].toString.constructor.foo'); + + expect(function() { + scope.$eval('{}.toString["constructor"]'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: {}.toString["constructor"]'); + expect(function() { + scope.$eval('{}["toString"]["constructor"]'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: {}["toString"]["constructor"]'); + + scope.a = []; + expect(function() { + scope.$eval('a.toString.constructor', scope); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: a.toString.constructor'); + expect(function() { + scope.$eval('a.toString["constructor"]', scope); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: a.toString["constructor"]'); + }); + + it('should NOT allow access to Function constructor in setter', function() { + expect(function() { + scope.$eval('{}.toString.constructor = 1'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString.constructor = 1'); + + expect(function() { + scope.$eval('{}.toString.constructor.a = 1'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString.constructor.a = 1'); + + expect(function() { + scope.$eval('{}.toString["constructor"]["constructor"] = 1'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString["constructor"]["constructor"] = 1'); + + + scope.key1 = "const"; + scope.key2 = "ructor"; + expect(function() { + scope.$eval('{}.toString[key1 + key2].foo'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: {}.toString[key1 + key2].foo'); + + expect(function() { + scope.$eval('{}.toString[key1 + key2] = 1'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString[key1 + key2] = 1'); + + expect(function() { + scope.$eval('{}.toString[key1 + key2].foo = 1'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: {}.toString[key1 + key2].foo = 1'); + + expect(function() { + scope.$eval('{}.toString["constructor"]["a"] = 1'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: {}.toString["constructor"]["a"] = 1'); + + scope.a = []; + expect(function() { + scope.$eval('a.toString.constructor = 1', scope); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: a.toString.constructor = 1'); + }); + + + it('should NOT allow access to Function constructor that has been aliased', function() { + scope.foo = { "bar": Function }; + expect(function() { + scope.$eval('foo["bar"]'); + }).toThrowMinErr( + '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' + + 'Expression: foo["bar"]'); + + }); + + + it('should NOT allow access to Function constructor in getter', function() { + expect(function() { + scope.$eval('{}.toString.constructor'); + }).toThrowMinErr( + '$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' + + 'Expression: {}.toString.constructor'); + }); + }); + + describe('Window and $element/node', function() { + configureEnvironment(cspEnabled, false); + it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) { + scope.wrap = {w: $window, d: $document}; + + expect(function() { + scope.$eval('wrap["w"]', scope); + }).toThrowMinErr( + '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + + 'disallowed! Expression: wrap["w"]'); + expect(function() { + scope.$eval('wrap["d"]', scope); + }).toThrowMinErr( + '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + + 'disallowed! Expression: wrap["d"]'); + })); + + it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) { + scope.getWin = valueFn($window); + scope.getDoc = valueFn($document); + + expect(function() { + scope.$eval('getWin()', scope); + }).toThrowMinErr( + '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + + 'disallowed! Expression: getWin()'); + expect(function() { + scope.$eval('getDoc()', scope); + }).toThrowMinErr( + '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + + 'disallowed! Expression: getDoc()'); + })); + + it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) { + scope.a = {b: { win: $window, doc: $document }}; + expect(function() { + scope.$eval('a.b.win.alert(1)', scope); + }).toThrowMinErr( + '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' + + 'disallowed! Expression: a.b.win.alert(1)'); + expect(function() { + scope.$eval('a.b.doc.on("click")', scope); + }).toThrowMinErr( + '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' + + 'disallowed! Expression: a.b.doc.on("click")'); + })); + }); + + }); + }); + + describe('promises', function() { var deferred, promise, q;