From 80c9140cf7e7a84425b37996a2ea9551fa53e9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Ram=C3=B3n=20L=C3=B3pez?= Date: Sat, 9 Nov 2013 20:58:16 +0100 Subject: [PATCH] feat($parse): Custom private field tagging Commit 3d6a89e introduced the concept of private fields that can not be evaluated on an Angular expression. This PR allows changing the matching pattern that tags a field as private. It also allows disabling the private field access restriction. Some examples: ```js myModule.config(function($parseProvider) { // disable private fields restriction $parseProvider.restrictFieldsMatching(null); }); ``` ```js myModule.config(function($parseProvider) { // Now private fields will be those whose identifier // begins with 'priv' $parseProvider.restrictFieldsMatching(/^priv/); }); ``` Custom private field identifiers allow backwards compatibility with AngularJS <1.2 projects that use underscores in scope fields (i.e. data retrieved from a MongoDB instance) while still enjoying the new private field security policy. --- docs/content/error/parse/iresre.ngdoc | 19 ++++++++++++ src/ng/parse.js | 33 ++++++++++++++++++++- test/ng/parseSpec.js | 42 +++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 docs/content/error/parse/iresre.ngdoc diff --git a/docs/content/error/parse/iresre.ngdoc b/docs/content/error/parse/iresre.ngdoc new file mode 100644 index 000000000000..81c471d7a28e --- /dev/null +++ b/docs/content/error/parse/iresre.ngdoc @@ -0,0 +1,19 @@ +@ngdoc error +@name $parse:iresre +@fullName Invalid matching pattern for tagging private fields +@description + +Occurs when a non-regexp expression is assigned as the +matching pattern used to tag private fields. + +Example that will throw this error: + +``` +$parseProvider.restrictFieldsMatching('priv'); +``` + +If you want to tag private fields with a 'priv' preffix +you should do this instead: +``` +$parseProvider.restrictFieldsMatching(/priv/); +``` diff --git a/src/ng/parse.js b/src/ng/parse.js index eeb60c4e4e1f..af5487c8b7e9 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -3,6 +3,7 @@ var $parseMinErr = minErr('$parse'); var promiseWarningCache = {}; var promiseWarning; +var restrictFieldsMatching = /^_|_$/; // Sandboxing Angular Expressions // ------------------------------ @@ -47,7 +48,7 @@ function ensureSafeMemberName(name, fullExpression, allowConstructor) { 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', fullExpression); } - if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') { + if (restrictFieldsMatching && (name.match(restrictFieldsMatching))) { throw $parseMinErr('isecprv', 'Referencing private fields in Angular expressions is disallowed! Expression: {0}', fullExpression); @@ -1207,6 +1208,36 @@ function $ParseProvider() { }; + /** + * @ngdoc method + * @name ng.$parseProvider#restrictFieldsMatching + * @methodOf ng.$parseProvider + * @description + * + * Allows using a custom regular expression for tagging private fields. You + * can turn off private fields by passing `null` as a parameter. + * + * The default is set to `/$_|_^/` which means every fields that begins or ends with an + * underscore. + * + * @param {RegExp=} value New value. + * @returns {RegExp} Returns the current setting + */ + this.restrictFieldsMatching = function(value) { + if (isDefined(value)) { + if ((value === null) || (value instanceof RegExp)) { + restrictFieldsMatching = value; + } + else { + throw $parseMinErr('iresre', + 'Invalid expression for matching private fields! Expression: {0}', + value); + } + } + return restrictFieldsMatching; + }; + + 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..1f2ebbb4b63c 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -192,10 +192,11 @@ describe('parser', function() { }); }); - var $filterProvider, scope; + var $filterProvider, scope, $parseProvider; - beforeEach(module(['$filterProvider', function (filterProvider) { + beforeEach(module(['$filterProvider', '$parseProvider', function (filterProvider, parseProvider) { $filterProvider = filterProvider; + $parseProvider = parseProvider; }])); @@ -592,6 +593,7 @@ describe('parser', function() { 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) { @@ -640,6 +642,42 @@ describe('parser', function() { testExpression('a["b"]["NAME"]'); testExpression('a["b"]["NAME"] = 1'); }); }); + + it('should allow disabling and custom tagging of private members', function() { + // test default behaviour + expect($parseProvider.restrictFieldsMatching()).toEqual(/^_|_$/); + scope._a = 1; + expect(function() { + scope.$eval('_a'); + }).toThrowMinErr( + '$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' + + 'Expression: _a'); + + // disable restricted field access + expect(function() { + $parseProvider.restrictFieldsMatching(null); + scope.$eval('_a'); + }).not.toThrow(); + + // custom restricted fields + scope.privateVar = 1; + expect(function() { + $parseProvider.restrictFieldsMatching(/^priv/); + scope.$eval('privateVar'); + }).toThrowMinErr( + '$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' + + 'Expression: privateVar'); + + // invalid custom expression + expect(function() { + $parseProvider.restrictFieldsMatching('priv'); + }).toThrowMinErr( + '$parse', 'iresre', 'Invalid expression for matching private fields! ' + + 'Expression: priv'); + + // set default value again + $parseProvider.restrictFieldsMatching(/^_|_$/); + }); }); describe('Function constructor', function() {