Pārlūkot izejas kodu

Restrict editing of MUC messages...

to ones with the same XEP-0421 occupant ID
JC Brand 2 gadi atpakaļ
vecāks
revīzija
7028286855

+ 1 - 0
CHANGES.md

@@ -10,6 +10,7 @@
 - Move the `converse-oauth` plugin to the [community-plugins](https://github.com/conversejs/community-plugins)
 - Don't apply message corrections when the MUC occupant-id doesn't match.
 - Update `nick` attribute on ChatRoom when user nickname changes
+- Restrict editing of MUC messages to ones with the same XEP-0421 occupant ID
 - #2936: Fix documentation about enable_smacks option, which is true by default.
 
 ## 9.1.1 (2022-05-05)

+ 1 - 1
src/headless/plugins/chat/model.js

@@ -234,7 +234,7 @@ const ChatBox = ModelWithContact.extend({
                 this.notifications.set('chat_state', attrs.chat_state);
             }
             if (u.shouldCreateMessage(attrs)) {
-                const msg = handleCorrection(this, attrs) || await this.createMessage(attrs);
+                const msg = await handleCorrection(this, attrs) || await this.createMessage(attrs);
                 this.notifications.set({'chat_state': null});
                 this.handleUnreadMessage(msg);
             }

+ 1 - 1
src/headless/plugins/muc/muc.js

@@ -2280,7 +2280,7 @@ const ChatRoomMixin = {
             this.updateNotifications(attrs.nick, attrs.chat_state);
         }
         if (u.shouldCreateGroupchatMessage(attrs)) {
-            const msg = handleCorrection(this, attrs) || (await this.createMessage(attrs));
+            const msg = await handleCorrection(this, attrs) || (await this.createMessage(attrs));
             this.removeNotification(attrs.nick, ['composing', 'paused']);
             this.handleUnreadMessage(msg);
         }

+ 5 - 2
src/headless/plugins/muc/parsers.js

@@ -245,12 +245,15 @@ export async function parseMUCMessage (stanza, chatbox) {
     const from_real_jid = attrs.is_archived && getJIDFromMUCUserData(stanza, attrs) ||
         chatbox.occupants.findOccupant(attrs)?.get('jid');
 
+    const own_occupant_id = chatbox.get('occupant_id');
+    const is_me = attrs.occupant_id && own_occupant_id ? own_occupant_id === attrs.occupant_id : attrs.nick === chatbox.get('nick');
+
     attrs = Object.assign( {
         from_real_jid,
         'is_only_emojis': attrs.body ? u.isOnlyEmojis(attrs.body) : false,
         'is_valid_receipt_request': isValidReceiptRequest(stanza, attrs),
-        'message': attrs.body || attrs.error, // TODO: Remove and use body and error attributes instead
-        'sender': attrs.nick === chatbox.get('nick') ? 'me' : 'them',
+        'message': attrs.body || attrs.error, // TODO: Should only be used for error and info messages
+        'sender': is_me ? 'me' : 'them',
     }, attrs);
 
     if (attrs.is_archived && original_stanza.getAttribute('from') !== attrs.from_muc) {

+ 3 - 2
src/headless/shared/chat/utils.js

@@ -72,7 +72,7 @@ export function getMediaURLs (arr, text, offset=0) {
  * @returns { _converse.Message|undefined } Returns the corrected
  *  message or `undefined` if not applicable.
  */
-export function handleCorrection (model, attrs) {
+export async function handleCorrection (model, attrs) {
     if (!attrs.replace_id || !attrs.from) {
         return;
     }
@@ -84,7 +84,8 @@ export function handleCorrection (model, attrs) {
 
     const message = model.messages.models.find(query);
     if (!message) {
-        return;
+        attrs['older_versions'] = [];
+        return await model.createMessage(attrs); // eslint-disable-line no-return-await
     }
 
     const older_versions = message.get('older_versions') || {};

+ 85 - 5
src/plugins/muc-views/tests/corrections.js

@@ -296,10 +296,11 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
                     from="lounge@montague.lit/newguy"
                     to="_converse.connection.jid"
                     type="groupchat"
-                    id="${msg_id}">
+                    id="${u.getUniqueId()}">
 
                     <body>But soft, what light through yonder chimney breaks?</body>
                     <occupant-id xmlns="urn:xmpp:occupant-id:0" id="2"></occupant-id>
+                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
                 </message>`
             );
 
@@ -309,7 +310,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
             expect(model.messages.at(0).get('edited')).toBeFalsy();
 
             expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder chimney breaks?');
-            expect(model.messages.at(1).get('edited')).toBeFalsy();
+            expect(model.messages.at(1).get('edited')).toBeTruthy();
 
             await model.handleMessageStanza(
                 stx`
@@ -317,9 +318,10 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
                     from="lounge@montague.lit/newguy"
                     to="_converse.connection.jid"
                     type="groupchat"
-                    id="${msg_id}">
+                    id="${u.getUniqueId()}">
 
                     <body>But soft, what light through yonder hatch breaks?</body>
+                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
                 </message>`
             );
 
@@ -329,13 +331,91 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
             expect(model.messages.at(0).get('edited')).toBeFalsy();
 
             expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder chimney breaks?');
-            expect(model.messages.at(1).get('edited')).toBeFalsy();
+            expect(model.messages.at(1).get('edited')).toBeTruthy();
 
             expect(model.messages.at(2).get('body')).toBe('But soft, what light through yonder hatch breaks?');
-            expect(model.messages.at(2).get('edited')).toBeFalsy();
+            expect(model.messages.at(2).get('edited')).toBeTruthy();
 
             const message_els = Array.from(view.querySelectorAll('.chat-msg'));
             expect(message_els.reduce((acc, m) => acc && u.hasClass('chat-msg--followup', m), true)).toBe(false);
         })
     );
+
+    it(
+        "cannot be edited if it's from a different occupant id",
+        mock.initConverse([], {}, async function (_converse) {
+            const nick = 'romeo';
+            const muc_jid = 'lounge@montague.lit';
+            const features = [...mock.default_muc_features, Strophe.NS.OCCUPANTID];
+            const model = await mock.openAndEnterChatRoom(_converse, muc_jid, nick, features);
+
+            expect(model.get('occupant_id')).toBe(model.occupants.at(0).get('occupant_id'));
+
+            const msg_id = u.getUniqueId();
+            await model.handleMessageStanza(
+                stx`
+                <message
+                    from="lounge@montague.lit/${nick}"
+                    to="_converse.connection.jid"
+                    type="groupchat"
+                    id="${msg_id}">
+
+                    <body>But soft, what light through yonder airlock breaks?</body>
+                    <occupant-id xmlns="urn:xmpp:occupant-id:0" id="${model.get('occupant_id')}"></occupant-id>
+                </message>`
+            );
+
+            const view = _converse.chatboxviews.get(muc_jid);
+            await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
+            expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder airlock breaks?');
+
+            await model.handleMessageStanza(
+                stx`
+                <message
+                    from="lounge@montague.lit/${nick}"
+                    to="_converse.connection.jid"
+                    type="groupchat"
+                    id="${u.getUniqueId()}">
+
+                    <body>But soft, what light through yonder chimney breaks?</body>
+                    <occupant-id xmlns="urn:xmpp:occupant-id:0" id="${model.get('occupant_id')}"></occupant-id>
+                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
+                </message>`
+            );
+
+            expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder chimney breaks?');
+            expect(model.messages.at(0).get('edited')).toBeTruthy();
+
+            await model.handleMessageStanza(
+                stx`
+                <message
+                    from="lounge@montague.lit/${nick}"
+                    to="_converse.connection.jid"
+                    type="groupchat"
+                    id="${u.getUniqueId()}">
+
+                    <body>But soft, what light through yonder hatch breaks?</body>
+                    <occupant-id xmlns="urn:xmpp:occupant-id:0" id="${u.getUniqueId()}"></occupant-id>
+                    <replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
+                </message>`
+            );
+
+            await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
+            expect(model.messages.length).toBe(2);
+            expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder chimney breaks?');
+            expect(model.messages.at(0).get('edited')).toBeTruthy();
+            expect(model.messages.at(0).get('editable')).toBeTruthy();
+
+            expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder hatch breaks?');
+            expect(model.messages.at(1).get('edited')).toBeTruthy();
+            expect(model.messages.at(1).get('editable')).toBeFalsy();
+
+            const message_els = Array.from(view.querySelectorAll('.chat-msg'));
+            expect(message_els.reduce((acc, m) => acc && u.hasClass('chat-msg--followup', m), true)).toBe(false);
+
+            // We can edit our own message, but not the other
+            expect(message_els[0].querySelector('converse-dropdown .chat-msg__action-edit')).toBeDefined();
+            expect(message_els[1].querySelector('converse-dropdown .chat-msg__action-edit')).toBe(null);
+        })
+    );
 });

+ 11 - 5
src/shared/components/message-versions.js

@@ -1,9 +1,13 @@
 import { CustomElement } from './element.js';
 import { api, converse } from '@converse/headless/core';
 import { html } from 'lit';
+import { __ } from 'i18n';
+import './styles/message-versions.scss';
 
 const { dayjs } = converse.env;
 
+const tpl_older_version = (k, older_versions) => html`<p class="older-msg"><time>${dayjs(k).format('MMM D, YYYY, HH:mm:ss')}</time>: ${older_versions[k]}</p>`;
+
 
 export class MessageVersions extends CustomElement {
 
@@ -15,13 +19,15 @@ export class MessageVersions extends CustomElement {
 
     render () {
         const older_versions = this.model.get('older_versions');
+        const keys = Object.keys(older_versions);
         return html`
-            <h4>Older versions</h4>
-            ${ Object.keys(older_versions).map(
-                k => html`<p class="older-msg"><time>${dayjs(k).format('MMM D, YYYY, HH:mm:ss')}</time>: ${older_versions[k]}</p>`) }
+            ${ keys.length ?
+                html`<h4>${__('Older versions')}</h4> ${keys.map(k => tpl_older_version(k, older_versions))}` :
+                html`<h4>${__('No older versions found')}</h4>`
+            }
             <hr/>
-            <h4>Current version</h4>
-            <p>${this.model.getMessageText()}</p>`;
+            <h4>${__('Current version')}</h4>
+            <p><time>${dayjs(this.model.get('time')).format('MMM D, YYYY, HH:mm:ss')}</time>: ${this.model.getMessageText()}</p>`;
     }
 }
 

+ 7 - 0
src/shared/components/styles/message-versions.scss

@@ -0,0 +1,7 @@
+.conversejs {
+    converse-message-versions {
+        time {
+            font-weight: bold;
+        }
+    }
+}

+ 0 - 6
src/shared/styles/messages.scss

@@ -3,12 +3,6 @@
         color: var(--subdued-color);
     }
 
-    .older-msg {
-        time {
-            font-weight: bold;
-        }
-    }
-
     .message {
         .show-msg-author-modal {
             align-self: flex-start; // Don't expand height to that of largest sibling