From 10adf5e8e9c50c4fc902fa1521529e2f8c9350f3 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 25 Mar 2020 16:10:45 -0600 Subject: [PATCH 1/2] fix(respondable): ignore reflected messages from iframes --- lib/core/utils/respondable.js | 11 +++--- lib/core/utils/uuid.js | 3 ++ test/core/utils/respondable.js | 53 +++++++++++++++++++++------ test/mock/frames/responder.html | 2 +- test/mock/frames/results-timeout.html | 2 +- test/mock/frames/throwing.html | 2 +- 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/lib/core/utils/respondable.js b/lib/core/utils/respondable.js index a2c2a0c276..db179a74b2 100644 --- a/lib/core/utils/respondable.js +++ b/lib/core/utils/respondable.js @@ -83,6 +83,7 @@ error: error, _respondable: true, _source: _getSource(), + _axeuuid: axe._uuid, _keepalive: keepalive }; @@ -213,7 +214,7 @@ 'message', function(e) { var data = parseMessage(e.data); - if (!data) { + if (!data || !data._axeuuid) { return; } @@ -221,13 +222,13 @@ /** * NOTE: messages from other contexts (frames) in response - * to a message should not contain a topic. We ignore these - * messages to prevent rogue postMessage handlers reflecting - * our messages. + * to a message should not contain the same axe._uuid. We + * ignore these messages to prevent rogue postMessage + * handlers reflecting our messages. * @see https://github.com/dequelabs/axe-core/issues/1754 */ var axeRespondables = axe._cache.get('axeRespondables') || {}; - if (axeRespondables[uuid] && data.topic && e.source !== window) { + if (axeRespondables[uuid] && data._axeuuid === axe._uuid) { return; } diff --git a/lib/core/utils/uuid.js b/lib/core/utils/uuid.js index af00cf4fbb..b08e3ec2cc 100644 --- a/lib/core/utils/uuid.js +++ b/lib/core/utils/uuid.js @@ -238,4 +238,7 @@ var uuid; uuid.parse = parse; uuid.unparse = unparse; uuid.BufferClass = BufferClass; + + // assign a unique id to this axe instance + axe._uuid = v1(); })(window); diff --git a/test/core/utils/respondable.js b/test/core/utils/respondable.js index 6843b48eaa..1dc29c5fcb 100644 --- a/test/core/utils/respondable.js +++ b/test/core/utils/respondable.js @@ -119,7 +119,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -140,7 +141,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.x.y.z', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -163,7 +165,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -187,7 +190,8 @@ describe('axe.utils.respondable', function() { _respondable: true, _source: 'axeAPI.2.0.0', message: 'Help us Obi-Wan', - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -206,7 +210,8 @@ describe('axe.utils.respondable', function() { event.initEvent('message', true, true); event.data = { _respondable: true, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }; event.source = window; @@ -225,7 +230,8 @@ describe('axe.utils.respondable', function() { event.data = JSON.stringify({ _respondable: true, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }) + 'joker tricks!'; event.source = window; @@ -243,7 +249,8 @@ describe('axe.utils.respondable', function() { event.initEvent('message', true, true); event.data = '{ "_respondable": true, "topic": "batman" }'; event.data = JSON.stringify({ - _respondable: true + _respondable: true, + _axeuuid: 'otherAxe' }); event.source = window; @@ -262,7 +269,8 @@ describe('axe.utils.respondable', function() { event.data = '{ "_respondable": true, "topic": "batman", "uuid": "12" }'; event.data = JSON.stringify({ _respondable: true, - uuid: 'not-' + mockUUID + uuid: 'not-' + mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -280,7 +288,8 @@ describe('axe.utils.respondable', function() { event.initEvent('message', true, true); event.data = '{ "uuid": "48", "topic": "batman" }'; event.data = JSON.stringify({ - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -304,7 +313,8 @@ describe('axe.utils.respondable', function() { message: 'The exhaust port is open!', trail: '... boom' }, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -332,7 +342,8 @@ describe('axe.utils.respondable', function() { message: 'The exhaust port is open!', trail: '... boom' }, - uuid: mockUUID + uuid: mockUUID, + _axeuuid: 'otherAxe' }); event.source = window; @@ -363,6 +374,26 @@ describe('axe.utils.respondable', function() { }); describe('subscribe', function() { + var origAxeUUID = axe._uuid; + var counter = 0; + + before(function() { + // assign axe a new uuid every time it's requested to trick + // the code that each respondable was called from a different + // context + Object.defineProperty(axe, '_uuid', { + get: function() { + return ++counter; + } + }); + }); + + after(function() { + Object.defineProperty(axe, '_uuid', { + value: origAxeUUID + }); + }); + it('should be a function', function() { assert.isFunction(axe.utils.respondable.subscribe); }); diff --git a/test/mock/frames/responder.html b/test/mock/frames/responder.html index e382bae67c..cc4856b9d9 100644 --- a/test/mock/frames/responder.html +++ b/test/mock/frames/responder.html @@ -4,13 +4,13 @@ Double responding frame - + diff --git a/test/mock/frames/results-timeout.html b/test/mock/frames/results-timeout.html index 011850f8b3..0df659e5b6 100644 --- a/test/mock/frames/results-timeout.html +++ b/test/mock/frames/results-timeout.html @@ -4,13 +4,13 @@ Message Iframe Fixture - + diff --git a/test/mock/frames/throwing.html b/test/mock/frames/throwing.html index 5618d35af1..a695304516 100644 --- a/test/mock/frames/throwing.html +++ b/test/mock/frames/throwing.html @@ -4,13 +4,13 @@ Error returning frame frame - + From 6a2113400510d196c6f9a48ddbfb01873ae530e6 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 26 Mar 2020 09:55:57 -0600 Subject: [PATCH 2/2] add test --- test/core/utils/respondable.js | 49 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/test/core/utils/respondable.js b/test/core/utils/respondable.js index 1dc29c5fcb..8df0380f84 100644 --- a/test/core/utils/respondable.js +++ b/test/core/utils/respondable.js @@ -124,6 +124,10 @@ describe('axe.utils.respondable', function() { }); event.source = window; + // this sets up the callback function but the post event from it + // is asynchronous so is will be ignored as the dispatch event + // that follows is synchronous and will trigger the callback + // (thats why there's no `done()` call) axe.utils.respondable(window, 'Death star', null, true, function(data) { success = true; assert.equal(data, 'Help us Obi-Wan'); @@ -247,9 +251,9 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "_respondable": true, "topic": "batman" }'; event.data = JSON.stringify({ _respondable: true, + topic: 'batman', _axeuuid: 'otherAxe' }); event.source = window; @@ -266,9 +270,9 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "_respondable": true, "topic": "batman", "uuid": "12" }'; event.data = JSON.stringify({ _respondable: true, + topic: 'batman', uuid: 'not-' + mockUUID, _axeuuid: 'otherAxe' }); @@ -286,9 +290,9 @@ describe('axe.utils.respondable', function() { var event = document.createEvent('Event'); // Define that the event name is 'build'. event.initEvent('message', true, true); - event.data = '{ "uuid": "48", "topic": "batman" }'; event.data = JSON.stringify({ uuid: mockUUID, + topic: 'batman', _axeuuid: 'otherAxe' }); event.source = window; @@ -300,6 +304,45 @@ describe('axe.utils.respondable', function() { assert.isTrue(success); }); + it('should reject messages that do not have `_axeuuid`', function() { + var success = true; + var event = document.createEvent('Event'); + // Define that the event name is 'build'. + event.initEvent('message', true, true); + event.data = JSON.stringify({ + _respondable: true, + topic: 'batman', + uuid: mockUUID + }); + event.source = window; + + axe.utils.respondable(window, 'batman', 'nananana', true, function() { + success = false; + }); + document.dispatchEvent(event); + assert.isTrue(success); + }); + + it('should reject messages from the same axe instance (`_axeuuid`)', function() { + var success = true; + var event = document.createEvent('Event'); + // Define that the event name is 'build'. + event.initEvent('message', true, true); + event.data = JSON.stringify({ + _respondable: true, + topic: 'batman', + _axeuuid: axe._uuid, + uuid: mockUUID + }); + event.source = window; + + axe.utils.respondable(window, 'batman', 'nananana', true, function() { + success = false; + }); + document.dispatchEvent(event); + assert.isTrue(success); + }); + it('should throw if an error message was send', function() { var success = false; var event = document.createEvent('Event');