From 3c1e2521694583bc1d8bade1ed5b162f5bfb065a Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Sun, 12 Jan 2020 13:06:56 +0100 Subject: [PATCH] fix: log error for illegal property access only once per property --- lib/handlebars/base.js | 8 +++++++ lib/handlebars/internal/proto-access.js | 28 ++++++++++++++++++------- spec/security.js | 21 +++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/handlebars/base.js b/lib/handlebars/base.js index a80e0dde4..6e58ad8b4 100644 --- a/lib/handlebars/base.js +++ b/lib/handlebars/base.js @@ -3,6 +3,7 @@ import Exception from './exception'; import { registerDefaultHelpers } from './helpers'; import { registerDefaultDecorators } from './decorators'; import logger from './logger'; +import { resetLoggedProperties } from './internal/proto-access'; export const VERSION = '4.7.0'; export const COMPILER_REVISION = 8; @@ -78,6 +79,13 @@ HandlebarsEnvironment.prototype = { }, unregisterDecorator: function(name) { delete this.decorators[name]; + }, + /** + * Reset the memory of illegal property accesses that have already been logged. + * @deprecated should only be used in handlebars test-cases + */ + resetLoggedPropertyAccesses() { + resetLoggedProperties(); } }; diff --git a/lib/handlebars/internal/proto-access.js b/lib/handlebars/internal/proto-access.js index a10f5124c..7bf9553f3 100644 --- a/lib/handlebars/internal/proto-access.js +++ b/lib/handlebars/internal/proto-access.js @@ -1,6 +1,8 @@ import { createNewLookupObject } from './create-new-lookup-object'; import * as logger from '../logger'; +const loggedProperties = Object.create(null); + export function createProtoAccessControl(runtimeOptions) { let defaultMethodWhiteList = Object.create(null); defaultMethodWhiteList['constructor'] = false; @@ -45,12 +47,24 @@ function checkWhiteList(protoAccessControlForType, propertyName) { if (protoAccessControlForType.defaultValue !== undefined) { return protoAccessControlForType.defaultValue; } - // eslint-disable-next-line no-console - logger.log( - 'error', - `Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` + - `You can add a runtime option to disable the check or this warning:\n` + - `See http://localhost:8080/api-reference/runtime-options.html#options-to-control-prototype-access for details` - ); + logUnexpecedPropertyAccessOnce(propertyName); return false; } + +function logUnexpecedPropertyAccessOnce(propertyName) { + if (loggedProperties[propertyName] !== true) { + loggedProperties[propertyName] = true; + logger.log( + 'error', + `Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` + + `You can add a runtime option to disable the check or this warning:\n` + + `See http://localhost:8080/api-reference/runtime-options.html#options-to-control-prototype-access for details` + ); + } +} + +export function resetLoggedProperties() { + Object.keys(loggedProperties).forEach(propertyName => { + delete loggedProperties[propertyName]; + }); +} diff --git a/spec/security.js b/spec/security.js index bf0be1485..1b345f0cc 100644 --- a/spec/security.js +++ b/spec/security.js @@ -190,6 +190,10 @@ describe('security issues', function() { return 'returnValue'; }; + beforeEach(function() { + handlebarsEnv.resetLoggedPropertyAccesses(); + }); + afterEach(function() { sinon.restore(); }); @@ -214,6 +218,23 @@ describe('security issues', function() { expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); }); + it('should only log the warning once', function() { + var spy = sinon.spy(console, 'error'); + + expectTemplate('{{aMethod}}') + .withInput(new TestClass()) + .withCompileOptions(compileOptions) + .toCompileTo(''); + + expectTemplate('{{aMethod}}') + .withInput(new TestClass()) + .withCompileOptions(compileOptions) + .toCompileTo(''); + + expect(spy.calledOnce).to.be.true(); + expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); + }); + it('can be allowed, which disables the warning', function() { var spy = sinon.spy(console, 'error');