Skip to content

Commit

Permalink
Merge pull request #2134 from dequelabs/respondable
Browse files Browse the repository at this point in the history
fix(respondable): ignore reflected messages from iframes
  • Loading branch information
straker committed Mar 27, 2020
2 parents 8890063 + 6a21134 commit 3ab9f21
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 21 deletions.
11 changes: 6 additions & 5 deletions lib/core/utils/respondable.js
Expand Up @@ -83,6 +83,7 @@
error: error,
_respondable: true,
_source: _getSource(),
_axeuuid: axe._uuid,
_keepalive: keepalive
};

Expand Down Expand Up @@ -213,21 +214,21 @@
'message',
function(e) {
var data = parseMessage(e.data);
if (!data) {
if (!data || !data._axeuuid) {
return;
}

var uuid = data.uuid;

/**
* 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;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/core/utils/uuid.js
Expand Up @@ -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);
100 changes: 87 additions & 13 deletions test/core/utils/respondable.js
Expand Up @@ -119,10 +119,15 @@ 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;

// 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');
Expand All @@ -140,7 +145,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;

Expand All @@ -163,7 +169,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;

Expand All @@ -187,7 +194,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;

Expand All @@ -206,7 +214,8 @@ describe('axe.utils.respondable', function() {
event.initEvent('message', true, true);
event.data = {
_respondable: true,
uuid: mockUUID
uuid: mockUUID,
_axeuuid: 'otherAxe'
};
event.source = window;

Expand All @@ -225,7 +234,8 @@ describe('axe.utils.respondable', function() {
event.data =
JSON.stringify({
_respondable: true,
uuid: mockUUID
uuid: mockUUID,
_axeuuid: 'otherAxe'
}) + 'joker tricks!';
event.source = window;

Expand All @@ -241,9 +251,10 @@ 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
_respondable: true,
topic: 'batman',
_axeuuid: 'otherAxe'
});
event.source = window;

Expand All @@ -259,10 +270,11 @@ 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,
uuid: 'not-' + mockUUID
topic: 'batman',
uuid: 'not-' + mockUUID,
_axeuuid: 'otherAxe'
});
event.source = window;

Expand All @@ -278,8 +290,48 @@ 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;

axe.utils.respondable(window, 'batman', 'nananana', true, function() {
success = false;
});
document.dispatchEvent(event);
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;
Expand All @@ -304,7 +356,8 @@ describe('axe.utils.respondable', function() {
message: 'The exhaust port is open!',
trail: '... boom'
},
uuid: mockUUID
uuid: mockUUID,
_axeuuid: 'otherAxe'
});
event.source = window;

Expand Down Expand Up @@ -332,7 +385,8 @@ describe('axe.utils.respondable', function() {
message: 'The exhaust port is open!',
trail: '... boom'
},
uuid: mockUUID
uuid: mockUUID,
_axeuuid: 'otherAxe'
});
event.source = window;

Expand Down Expand Up @@ -363,6 +417,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);
});
Expand Down
2 changes: 1 addition & 1 deletion test/mock/frames/responder.html
Expand Up @@ -4,13 +4,13 @@
<title>Double responding frame</title>
</head>
<body>
<script src="../../../lib/core/utils/uuid.js"></script>
<script>
var utils = {};
var axe = {
version: '2.0.0'
};
</script>
<script src="../../../lib/core/utils/uuid.js"></script>
<script src="../../../tmp/core/base/cache.js"></script>
<script src="../../../lib/core/utils/respondable.js"></script>

Expand Down
2 changes: 1 addition & 1 deletion test/mock/frames/results-timeout.html
Expand Up @@ -4,13 +4,13 @@
<title>Message Iframe Fixture</title>
</head>
<body>
<script src="../../../lib/core/utils/uuid.js"></script>
<script>
var utils = {};
var axe = {
version: '2.0.0'
};
</script>
<script src="../../../lib/core/utils/uuid.js"></script>
<script src="../../../tmp/core/base/cache.js"></script>
<script src="../../../lib/core/utils/respondable.js"></script>

Expand Down
2 changes: 1 addition & 1 deletion test/mock/frames/throwing.html
Expand Up @@ -4,13 +4,13 @@
<title>Error returning frame frame</title>
</head>
<body>
<script src="../../../lib/core/utils/uuid.js"></script>
<script>
var utils = {};
var axe = {
version: '2.0.0'
};
</script>
<script src="../../../lib/core/utils/uuid.js"></script>
<script src="../../../tmp/core/base/cache.js"></script>
<script src="../../../lib/core/utils/respondable.js"></script>

Expand Down

0 comments on commit 3ab9f21

Please sign in to comment.