diff --git a/karma.conf.js b/karma.conf.js index f8659fa4c..40701141f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -71,6 +71,7 @@ module.exports = function(config) { { pattern: "src/plugins/muc-views/tests/hats.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/http-file-upload.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/info-messages.js", type: 'module' }, + { pattern: "src/plugins/muc-views/tests/mam.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/markers.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/me-messages.js", type: 'module' }, { pattern: "src/plugins/muc-views/tests/mentions.js", type: 'module' }, diff --git a/package-lock.json b/package-lock.json index 27a9a25f5..2e66e213c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23255,6 +23255,7 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "devOptional": true, "engines": { "node": ">=8.3.0" }, @@ -41306,6 +41307,7 @@ "version": "7.4.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "devOptional": true, "requires": {} }, "xmlcreate": { diff --git a/src/plugins/muc-views/tests/mam.js b/src/plugins/muc-views/tests/mam.js new file mode 100644 index 000000000..d4961f83a --- /dev/null +++ b/src/plugins/muc-views/tests/mam.js @@ -0,0 +1,109 @@ +/*global mock, converse */ + +const { Strophe, $msg, $pres } = converse.env; +const u = converse.env.utils; + +describe("A MAM archived groupchat message", function () { + + it("is ignored if it has the same archive-id of an already received one", + mock.initConverse([], {}, async function (_converse) { + + const muc_jid = 'room@muc.example.com'; + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); + const view = _converse.chatboxviews.get(muc_jid); + spyOn(view.model, 'getDuplicateMessage').and.callThrough(); + let stanza = u.toStanza(` + + Typical body text + + `); + _converse.connection._dataRecv(mock.createRequest(stanza)); + await u.waitUntil(() => view.model.messages.length === 1); + await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 1); + let result = await view.model.getDuplicateMessage.calls.all()[0].returnValue; + expect(result).toBe(undefined); + + stanza = u.toStanza(` + + + + + + Typical body text + + + + `); + + spyOn(view.model, 'updateMessage'); + _converse.handleMAMResult(view.model, { 'messages': [stanza] }); + await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 2); + result = await view.model.getDuplicateMessage.calls.all()[1].returnValue; + expect(result instanceof _converse.Message).toBe(true); + expect(view.model.messages.length).toBe(1); + await u.waitUntil(() => view.model.updateMessage.calls.count()); + })); + + it("will be discarded if it's a malicious message meant to look like a carbon copy", + mock.initConverse([], {}, async function (_converse) { + + await mock.waitForRoster(_converse, 'current'); + await mock.openControlBox(_converse); + const muc_jid = 'xsf@muc.xmpp.org'; + const sender_jid = `${muc_jid}/romeo`; + const impersonated_jid = `${muc_jid}/i_am_groot` + await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); + const stanza = $pres({ + to: 'romeo@montague.lit/_converse.js-29092160', + from: sender_jid + }) + .c('x', {xmlns: Strophe.NS.MUC_USER}) + .c('item', { + 'affiliation': 'owner', + 'jid': 'newguy@montague.lit/_converse.js-290929789', + 'role': 'participant' + }).tree(); + _converse.connection._dataRecv(mock.createRequest(stanza)); + /* + * + * + * + * + * I am groot. + * + * + * + * + */ + const msg = $msg({ + 'from': sender_jid, + 'id': _converse.connection.getUniqueId(), + 'to': _converse.connection.jid, + 'type': 'groupchat', + 'xmlns': 'jabber:client' + }).c('received', {'xmlns': 'urn:xmpp:carbons:2'}) + .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) + .c('message', { + 'xmlns': 'jabber:client', + 'from': impersonated_jid, + 'to': muc_jid, + 'type': 'groupchat' + }).c('body').t('I am groot').tree(); + const view = _converse.chatboxviews.get(muc_jid); + spyOn(converse.env.log, 'error'); + await _converse.handleMAMResult(view.model, { 'messages': [msg] }); + await u.waitUntil(() => converse.env.log.error.calls.count()); + expect(converse.env.log.error).toHaveBeenCalledWith( + 'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied' + ); + expect(view.querySelectorAll('.chat-msg').length).toBe(0); + expect(view.model.messages.length).toBe(0); + })); +}); diff --git a/src/plugins/muc-views/tests/muc-messages.js b/src/plugins/muc-views/tests/muc-messages.js index 2a92f2d55..ee20dc63f 100644 --- a/src/plugins/muc-views/tests/muc-messages.js +++ b/src/plugins/muc-views/tests/muc-messages.js @@ -132,52 +132,6 @@ describe("A Groupchat Message", function () { expect(view.model.messages.length).toBe(2); })); - it("is ignored if it has the same archive-id of an already received one", - mock.initConverse([], {}, async function (_converse) { - - const muc_jid = 'room@muc.example.com'; - await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const view = _converse.chatboxviews.get(muc_jid); - spyOn(view.model, 'getDuplicateMessage').and.callThrough(); - let stanza = u.toStanza(` - - Typical body text - - `); - _converse.connection._dataRecv(mock.createRequest(stanza)); - await u.waitUntil(() => view.model.messages.length === 1); - await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 1); - let result = await view.model.getDuplicateMessage.calls.all()[0].returnValue; - expect(result).toBe(undefined); - - stanza = u.toStanza(` - - - - - - Typical body text - - - - `); - - spyOn(view.model, 'updateMessage'); - _converse.handleMAMResult(view.model, { 'messages': [stanza] }); - await u.waitUntil(() => view.model.getDuplicateMessage.calls.count() === 2); - result = await view.model.getDuplicateMessage.calls.all()[1].returnValue; - expect(result instanceof _converse.Message).toBe(true); - expect(view.model.messages.length).toBe(1); - await u.waitUntil(() => view.model.updateMessage.calls.count()); - })); - it("is ignored if it has the same stanza-id of an already received one", mock.initConverse([], {}, async function (_converse) { @@ -223,62 +177,6 @@ describe("A Groupchat Message", function () { await u.waitUntil(() => view.model.updateMessage.calls.count()); })); - it("will be discarded if it's a malicious message meant to look like a carbon copy", - mock.initConverse([], {}, async function (_converse) { - - await mock.waitForRoster(_converse, 'current'); - await mock.openControlBox(_converse); - const muc_jid = 'xsf@muc.xmpp.org'; - const sender_jid = `${muc_jid}/romeo`; - const impersonated_jid = `${muc_jid}/i_am_groot` - await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo'); - const stanza = $pres({ - to: 'romeo@montague.lit/_converse.js-29092160', - from: sender_jid - }) - .c('x', {xmlns: Strophe.NS.MUC_USER}) - .c('item', { - 'affiliation': 'owner', - 'jid': 'newguy@montague.lit/_converse.js-290929789', - 'role': 'participant' - }).tree(); - _converse.connection._dataRecv(mock.createRequest(stanza)); - /* - * - * - * - * - * I am groot. - * - * - * - * - */ - const msg = $msg({ - 'from': sender_jid, - 'id': _converse.connection.getUniqueId(), - 'to': _converse.connection.jid, - 'type': 'groupchat', - 'xmlns': 'jabber:client' - }).c('received', {'xmlns': 'urn:xmpp:carbons:2'}) - .c('forwarded', {'xmlns': 'urn:xmpp:forward:0'}) - .c('message', { - 'xmlns': 'jabber:client', - 'from': impersonated_jid, - 'to': muc_jid, - 'type': 'groupchat' - }).c('body').t('I am groot').tree(); - const view = _converse.chatboxviews.get(muc_jid); - spyOn(converse.env.log, 'error'); - await _converse.handleMAMResult(view.model, { 'messages': [msg] }); - await u.waitUntil(() => converse.env.log.error.calls.count()); - expect(converse.env.log.error).toHaveBeenCalledWith( - 'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied' - ); - expect(view.querySelectorAll('.chat-msg').length).toBe(0); - expect(view.model.messages.length).toBe(0); - })); - it("keeps track of the sender's role and affiliation", mock.initConverse([], {}, async function (_converse) {