Fauxton: Configurable escaping of notifications Sometimes we are passing static html-links to the notification- helper. With the new setting `escape: false` the escaping for the notifications can be disabled.
The default value is that all values are escaped, as long as no value with `false` is passed for the option escape. `undefined` and other falsy values will not unescape. I updated all occurences where we are passing html to the notifications, and added a comment, that people are careful, when the passed message changes to e.g. a dynamic content generated from users. Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/c57deb2b Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/c57deb2b Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/c57deb2b Branch: refs/heads/import-master Commit: c57deb2b7631b9f4e915edfaa738677ff15536c3 Parents: f423081 Author: Robert Kowalski <r...@kowalski.gd> Authored: Sun May 4 16:28:40 2014 +0200 Committer: Robert Kowalski <r...@kowalski.gd> Committed: Tue May 6 01:15:59 2014 +0200 ---------------------------------------------------------------------- app/addons/compaction/views.js | 7 ++-- app/addons/documents/views.js | 8 +++-- app/addons/fauxton/base.js | 11 ++++-- app/addons/fauxton/templates/notification.html | 2 +- app/addons/fauxton/tests/baseSpec.js | 39 ++++++++++++++++++++- 5 files changed, 57 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c57deb2b/app/addons/compaction/views.js ---------------------------------------------------------------------- diff --git a/app/addons/compaction/views.js b/app/addons/compaction/views.js index a68daef..be3f129 100644 --- a/app/addons/compaction/views.js +++ b/app/addons/compaction/views.js @@ -50,6 +50,7 @@ function (app, FauxtonAPI, Compaction) { FauxtonAPI.addNotification({ type: 'success', msg: 'Database compaction has started. Visit <a href="#activetasks">Active Tasks</a> to view the compaction progress.', + escape: false // beware of possible XSS when the message changes }); }, function (xhr, error, reason) { FauxtonAPI.addNotification({ @@ -70,7 +71,8 @@ function (app, FauxtonAPI, Compaction) { Compaction.cleanupViews(this.model).then(function () { FauxtonAPI.addNotification({ type: 'success', - msg: 'View cleanup has started. Visit <a href="#activetasks">Active Tasks</a> to view progress.' + msg: 'View cleanup has started. Visit <a href="#activetasks">Active Tasks</a> to view progress.', + escape: false // beware of possible XSS when the message changes }); }, function (xhr, error, reason) { FauxtonAPI.addNotification({ @@ -120,7 +122,8 @@ function (app, FauxtonAPI, Compaction) { Compaction.compactView(this.database, this.designDoc).then(function () { FauxtonAPI.addNotification({ type: 'success', - msg: 'View compaction has started. Visit <a href="#activetasks">Active Tasks</a> to view progress.' + msg: 'View compaction has started. Visit <a href="#activetasks">Active Tasks</a> to view progress.', + escape: false // beware of possible XSS when the message changes }); }, function (xhr, error, reason) { FauxtonAPI.addNotification({ http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c57deb2b/app/addons/documents/views.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/views.js b/app/addons/documents/views.js index 05ee6a2..896f19b 100644 --- a/app/addons/documents/views.js +++ b/app/addons/documents/views.js @@ -111,8 +111,9 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, this.database.destroy().then(function () { FauxtonAPI.navigate('#/_all_dbs'); FauxtonAPI.addNotification({ - msg: 'The database <code>' + databaseName + '</code> has been deleted.', - clear: true + msg: 'The database <code>' + _.escape(databaseName) + '</code> has been deleted.', + clear: true, + escape: false // beware of possible XSS when the message changes }); }).fail(function (rsp, error, msg) { FauxtonAPI.addNotification({ @@ -1594,7 +1595,8 @@ function(app, FauxtonAPI, Components, Documents, Databases, pouchdb, msg: "<strong>Warning!</strong> Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB.", type: "warning", selector: ".advanced-options .errors-container", - fade: true + fade: true, + escape: false // beware of possible XSS when the message changes }); var promise = FauxtonAPI.Deferred(); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c57deb2b/app/addons/fauxton/base.js ---------------------------------------------------------------------- diff --git a/app/addons/fauxton/base.js b/app/addons/fauxton/base.js index 861c544..bfc7d31 100644 --- a/app/addons/fauxton/base.js +++ b/app/addons/fauxton/base.js @@ -23,7 +23,8 @@ function(app, FauxtonAPI, resizeColumns) { options = _.extend({ msg: "Notification Event Triggered!", type: "info", - selector: "#global-notifications" + selector: "#global-notifications", + escape: true }, options); var view = new Fauxton.Notification(options); @@ -304,7 +305,11 @@ function(app, FauxtonAPI, resizeColumns) { fadeTimer: 5000, initialize: function(options) { - this.msg = options.msg; + this.htmlToRender = options.msg; + // escape always, except the value is false + if (options.escape !== false) { + this.htmlToRender = _.escape(this.htmlToRender); + } this.type = options.type || "info"; this.selector = options.selector; this.fade = options.fade === undefined ? true : options.fade; @@ -316,7 +321,7 @@ function(app, FauxtonAPI, resizeColumns) { serialize: function() { return { data: this.data, - msg: this.msg, + htmlToRender: this.htmlToRender, type: this.type }; }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c57deb2b/app/addons/fauxton/templates/notification.html ---------------------------------------------------------------------- diff --git a/app/addons/fauxton/templates/notification.html b/app/addons/fauxton/templates/notification.html index 1cf121b..de0337c 100644 --- a/app/addons/fauxton/templates/notification.html +++ b/app/addons/fauxton/templates/notification.html @@ -14,5 +14,5 @@ the License. <div class="alert alert-<%- type %>"> <button type="button" class="close" data-dismiss="alert">Ã</button> - <%- msg %> + <%= htmlToRender %><!-- every caller has to escape on it's own --> </div> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/c57deb2b/app/addons/fauxton/tests/baseSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/fauxton/tests/baseSpec.js b/app/addons/fauxton/tests/baseSpec.js index 7df4dfc..a7ff7a1 100644 --- a/app/addons/fauxton/tests/baseSpec.js +++ b/app/addons/fauxton/tests/baseSpec.js @@ -49,7 +49,7 @@ define([ hooks: [], setBreadcrumbs: sinon.spy(), apiBar: apiBar, - layout: { + layout: { setView: function () {} } }; @@ -75,5 +75,42 @@ define([ }); +describe('Fauxton Notifications', function () { + it('should escape by default', function () { + window.fauxton_xss_test_escaped = true; + var view = FauxtonAPI.addNotification({ + msg: '<script>window.fauxton_xss_test_escaped = false;</script>', + selector: 'body' + }); + view.$el.remove(); + assert.ok(window.fauxton_xss_test_escaped); + delete window.fauxton_xss_test_escaped; + }); + + it('should be able to render unescaped', function () { + var view = FauxtonAPI.addNotification({ + msg: '<script>window.fauxton_xss_test_unescaped = true;</script>', + selector: 'body', + escape: false + }); + view.$el.remove(); + assert.ok(window.fauxton_xss_test_unescaped); + delete window.fauxton_xss_test_unescaped; + }); + + it('should render escaped if the escape value is not explicitly false,' + + 'e.g. was forgotten in a direct call', function () { + + window.fauxton_xss_test2_escaped = true; + var view = new Base.Notification({ + msg: '<script>window.fauxton_xss_test2_escaped = false;</script>', + selector: 'body' + }).render(); + view.$el.remove(); + assert.ok(window.fauxton_xss_test2_escaped); + delete window.fauxton_xss_test2_escaped; + }); + + }); });