From 8d8076aea0608179a120cbd82faaafd1c9a7f71b Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 11:30:48 +0000 Subject: [PATCH 01/19] Add a new column for tracking replacements of events --- .../schema/delta/56/event_labels_edit.sql | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 synapse/storage/data_stores/main/schema/delta/56/event_labels_edit.sql diff --git a/synapse/storage/data_stores/main/schema/delta/56/event_labels_edit.sql b/synapse/storage/data_stores/main/schema/delta/56/event_labels_edit.sql new file mode 100644 index 000000000000..7a319990fa71 --- /dev/null +++ b/synapse/storage/data_stores/main/schema/delta/56/event_labels_edit.sql @@ -0,0 +1,23 @@ +/* Copyright 2019 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +-- Track the ID of the event this event replaces (with a "m.replace" relation). This +-- exists so we can keep track of the changes in the list of labels associated with a +-- message. +ALTER TABLE event_labels ADD COLUMN replaces TEXT; + +-- We need this index because we'll be querying labels which are either for a specific +-- event or for events that replace it. +CREATE INDEX event_labels_replaces_idx ON event_labels(replaces); \ No newline at end of file From c943cda32b16e9bc3d7dec97f5c5f117775636a7 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 15:57:56 +0000 Subject: [PATCH 02/19] Support edits in insert_labels_for_event_txn --- synapse/storage/data_stores/main/events.py | 49 +++++++++++++++------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 878f7568a63d..56e5eaff01b8 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1930,31 +1930,48 @@ def get_all_updated_current_state_deltas_txn(txn): ) def insert_labels_for_event_txn( - self, txn, event_id, labels, room_id, topological_ordering + self, txn, event_id, replaces, labels, room_id, topological_ordering ): """Store the mapping between an event's ID and its labels, with one row per - (event_id, label) tuple. + (event_id, label) tuple, after deleting labels associated with the event it + replaces (or a prior replacement of it), if applicable. + + Can be called with labels being None, in which case it will only delete labels + associated with the replaced event. Args: txn (LoggingTransaction): The transaction to execute. event_id (str): The event's ID. - labels (list[str]): A list of text labels. + replaces (str|None): The ID of the event this event replaces, if any. + labels (list[str]|None): A list of text labels, if any. room_id (str): The ID of the room the event was sent to. topological_ordering (int): The position of the event in the room's topology. """ - return self._simple_insert_many_txn( - txn=txn, - table="event_labels", - values=[ - { - "event_id": event_id, - "label": label, - "room_id": room_id, - "topological_ordering": topological_ordering, - } - for label in labels - ], - ) + if replaces: + # If the event is replacing another one (e.g. it's an edit), delete all + # labels associated with the event it replaces or past replacements of it. + sql = """ + DELETE FROM event_labels + WHERE event_id = ? OR replaces = ? + """ + + txn.execute(sql, (replaces, replaces)) + + if labels: + self._simple_insert_many_txn( + txn=txn, + table="event_labels", + values=[ + { + "event_id": event_id, + "label": label, + "room_id": room_id, + "topological_ordering": topological_ordering, + "replaces": replaces, + } + for label in labels + ], + ) AllNewEventsResult = namedtuple( From 512b298aa7da2502bf3cd0b87c397b1c782bc17b Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 16:29:08 +0000 Subject: [PATCH 03/19] Add more checks before storing labels --- synapse/storage/data_stores/main/events.py | 63 +++++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 56e5eaff01b8..89bc68823138 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -29,7 +29,7 @@ from twisted.internet import defer import synapse.metrics -from synapse.api.constants import EventContentFields, EventTypes +from synapse.api.constants import EventContentFields, EventTypes, RelationTypes from synapse.api.errors import SynapseError from synapse.events import EventBase # noqa: F401 from synapse.events.snapshot import EventContext # noqa: F401 @@ -931,13 +931,7 @@ def _update_metadata_tables_txn( self._store_redaction(txn, event) self._handle_event_relations(txn, event) - - # Store the labels for this event. - labels = event.content.get(EventContentFields.LABELS) - if labels: - self.insert_labels_for_event_txn( - txn, event.event_id, labels, event.room_id, event.depth - ) + self._store_labels_txn(txn, event) # Insert into the room_memberships table. self._store_room_members_txn( @@ -1929,6 +1923,59 @@ def get_all_updated_current_state_deltas_txn(txn): get_all_updated_current_state_deltas_txn, ) + def _store_labels_txn(self, txn, event): + """Extract labels from an event and store them. + + Args: + txn (LoggingTransaction): The transaction to execute. + event (EventBase): The event to process. + """ + # Check if the event replaces another one (e.g. it's an edit) + relation = event.content.get("m.relates_to", {}) + replaces = None + if relation.get("rel_type") == RelationTypes.REPLACE: + replaces = relation.get("event_id") + + # Check if we already know about labels for this event, or for the original event + # if this one has a m.replace relation. + old_labels = self.get_labels_for_event_txn( + txn=txn, + event_id=replaces if replaces is not None else event.event_id, + ) + + # Check if this event has any labels attached to it. + labels = event.content.get(EventContentFields.LABELS) + + # If there isn't any label related to this event and the new event doesn't add any + # label, then do nothing. + if not old_labels and not labels: + return + + # Otherwise, insert the labels. If we got previous labels for the event this one + # replaces but this event specifically doesn't have any, the insert function will + # delete all labels previously associated with the event it replaces and insert + # nothing. + self.insert_labels_for_event_txn( + txn, event.event_id, replaces, labels, event.room_id, event.depth + ) + + @cached() + def get_labels_for_event_txn(self, txn, event_id): + labels = [] + + txn.execute( + """ + SELECT label FROM event_labels + WHERE event_id = ? OR replaces = ? + """, + (event_id, event_id), + ) + + for label in txn: + labels.append(label) + + return labels + def insert_labels_for_event_txn( self, txn, event_id, replaces, labels, room_id, topological_ordering ): From d42f5c103dffdf1f912bbc4765fd0f221f8c6369 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 16:31:10 +0000 Subject: [PATCH 04/19] Invalidate cache on labels update --- synapse/storage/data_stores/main/events.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 89bc68823138..2f65d2debca1 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1939,8 +1939,8 @@ def _store_labels_txn(self, txn, event): # Check if we already know about labels for this event, or for the original event # if this one has a m.replace relation. old_labels = self.get_labels_for_event_txn( - txn=txn, event_id=replaces if replaces is not None else event.event_id, + txn=txn, ) # Check if this event has any labels attached to it. @@ -1959,8 +1959,8 @@ def _store_labels_txn(self, txn, event): txn, event.event_id, replaces, labels, event.room_id, event.depth ) - @cached() - def get_labels_for_event_txn(self, txn, event_id): + @cached(num_args=1) + def get_labels_for_event_txn(self, event_id, txn): labels = [] txn.execute( @@ -2020,6 +2020,8 @@ def insert_labels_for_event_txn( ], ) + self._invalidate_cache_and_stream(txn, self.get_labels_for_event_txn, event_id) + AllNewEventsResult = namedtuple( "AllNewEventsResult", From 6967740ed68274171e3b85a808d052fab52f7e54 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 16:48:26 +0000 Subject: [PATCH 05/19] Docstring --- synapse/storage/data_stores/main/events.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 2f65d2debca1..edec959369c9 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1961,6 +1961,12 @@ def _store_labels_txn(self, txn, event): @cached(num_args=1) def get_labels_for_event_txn(self, event_id, txn): + """Retrieve the list of labels for a given event. + + Args: + event_id (str): The event's ID. + txn (LoggingTransaction): The transaction to execute. + """ labels = [] txn.execute( From 9baafc86397915b6bf60e8bb115272c6343ebd38 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 17:09:21 +0000 Subject: [PATCH 06/19] Remove useless checks --- synapse/storage/data_stores/main/events.py | 45 ++-------------------- 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index edec959369c9..e17756b1abd1 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1936,52 +1936,14 @@ def _store_labels_txn(self, txn, event): if relation.get("rel_type") == RelationTypes.REPLACE: replaces = relation.get("event_id") - # Check if we already know about labels for this event, or for the original event - # if this one has a m.replace relation. - old_labels = self.get_labels_for_event_txn( - event_id=replaces if replaces is not None else event.event_id, - txn=txn, - ) - - # Check if this event has any labels attached to it. + # Retrieve the labels on this event. labels = event.content.get(EventContentFields.LABELS) - # If there isn't any label related to this event and the new event doesn't add any - # label, then do nothing. - if not old_labels and not labels: - return - - # Otherwise, insert the labels. If we got previous labels for the event this one - # replaces but this event specifically doesn't have any, the insert function will - # delete all labels previously associated with the event it replaces and insert - # nothing. + # Insert the labels, if any. self.insert_labels_for_event_txn( txn, event.event_id, replaces, labels, event.room_id, event.depth ) - @cached(num_args=1) - def get_labels_for_event_txn(self, event_id, txn): - """Retrieve the list of labels for a given event. - - Args: - event_id (str): The event's ID. - txn (LoggingTransaction): The transaction to execute. - """ - labels = [] - - txn.execute( - """ - SELECT label FROM event_labels - WHERE event_id = ? OR replaces = ? - """, - (event_id, event_id), - ) - - for label in txn: - labels.append(label) - - return labels - def insert_labels_for_event_txn( self, txn, event_id, replaces, labels, room_id, topological_ordering ): @@ -2010,6 +1972,7 @@ def insert_labels_for_event_txn( txn.execute(sql, (replaces, replaces)) + # If the event doesn't have any label, don't insert anything. if labels: self._simple_insert_many_txn( txn=txn, @@ -2026,8 +1989,6 @@ def insert_labels_for_event_txn( ], ) - self._invalidate_cache_and_stream(txn, self.get_labels_for_event_txn, event_id) - AllNewEventsResult = namedtuple( "AllNewEventsResult", From 3b36b930a4a1b83e5b6d2b279d14b0e2b27d092f Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Mon, 18 Nov 2019 17:25:46 +0000 Subject: [PATCH 07/19] Changelog --- changelog.d/6374.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6374.feature diff --git a/changelog.d/6374.feature b/changelog.d/6374.feature new file mode 100644 index 000000000000..78a187a1dc1c --- /dev/null +++ b/changelog.d/6374.feature @@ -0,0 +1 @@ +Implement label-based filtering on `/sync` and `/messages` ([MSC2326](https://github.com/matrix-org/matrix-doc/pull/2326)). From 84ba9f991aa4b54e22368ead696f9a0fefb5782c Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 20 Nov 2019 16:12:07 +0000 Subject: [PATCH 08/19] Update changelog since this isn't going to be featured in 1.6.0 --- changelog.d/6374.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/6374.feature b/changelog.d/6374.feature index 78a187a1dc1c..f65405017131 100644 --- a/changelog.d/6374.feature +++ b/changelog.d/6374.feature @@ -1 +1 @@ -Implement label-based filtering on `/sync` and `/messages` ([MSC2326](https://github.com/matrix-org/matrix-doc/pull/2326)). +Make the event labelling feature support edits. From fdefe8147fb25cf8a7a42356b069e751df7cef93 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 21 Nov 2019 14:39:08 +0000 Subject: [PATCH 09/19] Don't save labels that are not string --- synapse/storage/data_stores/main/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index e17756b1abd1..e9b544561a3a 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1986,6 +1986,7 @@ def insert_labels_for_event_txn( "replaces": replaces, } for label in labels + if isinstance(label, str) ], ) From 13f42c5901730645a84599caecc11cf8564f2570 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 21 Nov 2019 15:11:48 +0000 Subject: [PATCH 10/19] Check processing an edit doesn't overwrite the processing of a more recent edit of the same event --- synapse/storage/data_stores/main/events.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index e9b544561a3a..08dfca4fe00f 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1963,6 +1963,27 @@ def insert_labels_for_event_txn( topological_ordering (int): The position of the event in the room's topology. """ if replaces: + # Check that processing that edit won't overwrite the processing of a more + # recent edit of the same event. + txn.execute( + """ + SELECT topological_ordering FROM event_labels + WHERE event_id = ? OR replaces = ? + """, + (replaces, replaces), + ) + + # We don't care about which row we're using because they'll all have the same + # topological ordering, since they're all about the same event. + # If the topological ordering (depth) of the current event is lower than the + # one of the event which labels are already stored (which means the current + # event is less recent), then stop here because we already have a more recent + # list of labels. + # FIXME: This isn't true if the most recent edit removes every label from the + # event, what should we do in that case? + if topological_ordering <= txn[0][0]: + return + # If the event is replacing another one (e.g. it's an edit), delete all # labels associated with the event it replaces or past replacements of it. sql = """ From 6b7d1c52ec347fe581698ad4926866997ad4db9c Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 21 Nov 2019 15:12:24 +0000 Subject: [PATCH 11/19] Make the background update use insert_labels_for_event_txn --- .../data_stores/main/events_bg_updates.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/synapse/storage/data_stores/main/events_bg_updates.py b/synapse/storage/data_stores/main/events_bg_updates.py index 0ed59ef48ea9..53560f361298 100644 --- a/synapse/storage/data_stores/main/events_bg_updates.py +++ b/synapse/storage/data_stores/main/events_bg_updates.py @@ -21,7 +21,7 @@ from twisted.internet import defer -from synapse.api.constants import EventContentFields +from synapse.api.constants import EventContentFields, RelationTypes from synapse.storage._base import make_in_list_sql_clause from synapse.storage.background_updates import BackgroundUpdateStore @@ -532,21 +532,24 @@ def _event_store_labels_txn(txn): for (event_id, event_json_raw) in results: event_json = json.loads(event_json_raw) - self._simple_insert_many_txn( + # Check if the event replaces another one (e.g. it's an edit) + relation = event_json["content"].get("m.relates_to", {}) + replaces = None + if relation.get("rel_type") == RelationTypes.REPLACE: + replaces = relation.get("event_id") + + # Extract the labels from the event's JSON + labels = event_json["content"].get(EventContentFields.LABELS, []) + + # Inserts the labels in the database table. This function will take care + # of processing the edit (if it's one) correctly. + self.insert_labels_for_event_txn( txn=txn, - table="event_labels", - values=[ - { - "event_id": event_id, - "label": label, - "room_id": event_json["room_id"], - "topological_ordering": event_json["depth"], - } - for label in event_json["content"].get( - EventContentFields.LABELS, [] - ) - if isinstance(label, str) - ], + event_id=event_id, + replaces=replaces, + labels=labels, + room_id=event_json["room_id"], + topological_ordering=event_json["depth"], ) nbrows += 1 From 6a83a5ee662ff44a6d80050db4ee1804a5e8c2f8 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 21 Nov 2019 16:35:23 +0000 Subject: [PATCH 12/19] Don't return if we couldn't get a topological token --- synapse/storage/data_stores/main/events.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 08dfca4fe00f..de48ecb73be5 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1973,6 +1973,7 @@ def insert_labels_for_event_txn( (replaces, replaces), ) + txn.execute("SELECT 1") # We don't care about which row we're using because they'll all have the same # topological ordering, since they're all about the same event. # If the topological ordering (depth) of the current event is lower than the @@ -1981,7 +1982,8 @@ def insert_labels_for_event_txn( # list of labels. # FIXME: This isn't true if the most recent edit removes every label from the # event, what should we do in that case? - if topological_ordering <= txn[0][0]: + results = tuple(txn) + if not results or topological_ordering <= results[0][0]: return # If the event is replacing another one (e.g. it's an edit), delete all From e0e6384a953fb95b333701d3d80704c40183940d Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 4 Dec 2019 14:48:26 +0000 Subject: [PATCH 13/19] Update synapse/storage/data_stores/main/events.py Co-Authored-By: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- synapse/storage/data_stores/main/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index de48ecb73be5..8a1c24b043b4 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1927,7 +1927,7 @@ def _store_labels_txn(self, txn, event): """Extract labels from an event and store them. Args: - txn (LoggingTransaction): The transaction to execute. + txn (LoggingTransaction): database cursor event (EventBase): The event to process. """ # Check if the event replaces another one (e.g. it's an edit) From 31ef3ae134361d5b7f68b69a9c0d9134a13f7952 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 4 Dec 2019 16:26:58 +0000 Subject: [PATCH 14/19] Only process edits if sent by the original event's sender --- synapse/storage/data_stores/main/events.py | 37 +++++++++++++++++-- .../data_stores/main/events_bg_updates.py | 1 + 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index c6cf4b5b697b..f977775a2c25 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -1928,7 +1928,7 @@ def _store_labels_txn(self, txn, event): """Extract labels from an event and store them. Args: - txn (LoggingTransaction): database cursor + txn (LoggingTransaction): The database cursor to use. event (EventBase): The event to process. """ # Check if the event replaces another one (e.g. it's an edit) @@ -1942,11 +1942,17 @@ def _store_labels_txn(self, txn, event): # Insert the labels, if any. self.insert_labels_for_event_txn( - txn, event.event_id, replaces, labels, event.room_id, event.depth + txn=txn, + event_id=event.event_id, + sender=event.sender, + replaces=replaces, + labels=labels, + room_id=event.room_id, + topological_ordering=event.depth, ) def insert_labels_for_event_txn( - self, txn, event_id, replaces, labels, room_id, topological_ordering + self, txn, event_id, replaces, sender, labels, room_id, topological_ordering ): """Store the mapping between an event's ID and its labels, with one row per (event_id, label) tuple, after deleting labels associated with the event it @@ -1955,15 +1961,38 @@ def insert_labels_for_event_txn( Can be called with labels being None, in which case it will only delete labels associated with the replaced event. + If the event is an edit and the provided sender doesn't match with the sender + of the original event, log and return. + Args: - txn (LoggingTransaction): The transaction to execute. + txn (LoggingTransaction): The database cursor to use. event_id (str): The event's ID. replaces (str|None): The ID of the event this event replaces, if any. + sender (str): The sender of the event. labels (list[str]|None): A list of text labels, if any. room_id (str): The ID of the room the event was sent to. topological_ordering (int): The position of the event in the room's topology. """ if replaces: + # Make sure that the sender of the edit match the original event's sender, + # otherwise ignore this edit. We do this here because _store_labels_txn and + # the background update converge here. + original_sender = self._simple_select_one_onecol_txn( + txn=txn, + table="events", + keyvalues={"event_id": replaces}, + retcol="sender", + ) + + if sender != original_sender: + logger.error( + "The edit's sender of edit %s doesn't match the sender of the " + "original event %s, not updating labels.", + sender, + original_sender, + ) + return + # Check that processing that edit won't overwrite the processing of a more # recent edit of the same event. txn.execute( diff --git a/synapse/storage/data_stores/main/events_bg_updates.py b/synapse/storage/data_stores/main/events_bg_updates.py index b2d695c0e487..5bb088fa6ddd 100644 --- a/synapse/storage/data_stores/main/events_bg_updates.py +++ b/synapse/storage/data_stores/main/events_bg_updates.py @@ -548,6 +548,7 @@ def _event_store_labels_txn(txn): txn=txn, event_id=event_id, replaces=replaces, + sender=event_json["sender"], labels=labels, room_id=event_json["room_id"], topological_ordering=event_json["depth"], From 929043ac9c0c11b58cfc26081ebeef4cce2c0d63 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Wed, 4 Dec 2019 16:51:43 +0000 Subject: [PATCH 15/19] Make _send_labelled_messages_in_room return every event ID --- tests/rest/client/v1/test_rooms.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py index 1ca7fa742fe9..18f8acfe3992 100644 --- a/tests/rest/client/v1/test_rooms.py +++ b/tests/rest/client/v1/test_rooms.py @@ -1258,7 +1258,8 @@ def prepare(self, reactor, clock, homeserver): def test_context_filter_labels(self): """Test that we can filter by a label on a /context request.""" - event_id = self._send_labelled_messages_in_room() + res = self._send_labelled_messages_in_room() + event_id = res[3] request, channel = self.make_request( "GET", @@ -1289,7 +1290,8 @@ def test_context_filter_labels(self): def test_context_filter_not_labels(self): """Test that we can filter by the absence of a label on a /context request.""" - event_id = self._send_labelled_messages_in_room() + res = self._send_labelled_messages_in_room() + event_id = res[3] request, channel = self.make_request( "GET", @@ -1325,7 +1327,8 @@ def test_context_filter_labels_not_labels(self): """Test that we can filter by both a label and the absence of another label on a /context request. """ - event_id = self._send_labelled_messages_in_room() + res = self._send_labelled_messages_in_room() + event_id = res[3] request, channel = self.make_request( "GET", @@ -1536,7 +1539,9 @@ def _send_labelled_messages_in_room(self): Returns: The ID of the event to use if we're testing filtering on /context. """ - self.helper.send_event( + event_ids = [] + + res = self.helper.send_event( room_id=self.room_id, type=EventTypes.Message, content={ @@ -1546,13 +1551,15 @@ def _send_labelled_messages_in_room(self): }, tok=self.tok, ) + event_ids.append(res["event_id"]) - self.helper.send_event( + res = self.helper.send_event( room_id=self.room_id, type=EventTypes.Message, content={"msgtype": "m.text", "body": "without label"}, tok=self.tok, ) + event_ids.append(res["event_id"]) res = self.helper.send_event( room_id=self.room_id, @@ -1560,10 +1567,9 @@ def _send_labelled_messages_in_room(self): content={"msgtype": "m.text", "body": "without label"}, tok=self.tok, ) - # Return this event's ID when we test filtering in /context requests. - event_id = res["event_id"] + event_ids.append(res["event_id"]) - self.helper.send_event( + res = self.helper.send_event( room_id=self.room_id, type=EventTypes.Message, content={ @@ -1573,8 +1579,9 @@ def _send_labelled_messages_in_room(self): }, tok=self.tok, ) + event_ids.append(res["event_id"]) - self.helper.send_event( + res = self.helper.send_event( room_id=self.room_id, type=EventTypes.Message, content={ @@ -1584,6 +1591,7 @@ def _send_labelled_messages_in_room(self): }, tok=self.tok, ) + event_ids.append(res["event_id"]) self.helper.send_event( room_id=self.room_id, @@ -1595,5 +1603,6 @@ def _send_labelled_messages_in_room(self): }, tok=self.tok, ) + event_ids.append(res["event_id"]) - return event_id + return event_ids From ec063fc2d7010cc7668332891be6722fd71638ad Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Thu, 5 Dec 2019 12:13:09 +0000 Subject: [PATCH 16/19] Add unit test --- tests/rest/client/v1/test_rooms.py | 130 ++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py index 18f8acfe3992..40637527176a 100644 --- a/tests/rest/client/v1/test_rooms.py +++ b/tests/rest/client/v1/test_rooms.py @@ -26,7 +26,12 @@ from twisted.internet import defer import synapse.rest.admin -from synapse.api.constants import EventContentFields, EventTypes, Membership +from synapse.api.constants import ( + EventContentFields, + EventTypes, + Membership, + RelationTypes, +) from synapse.handlers.pagination import PurgeStatus from synapse.rest.client.v1 import login, profile, room from synapse.util.stringutils import random_string @@ -1256,6 +1261,11 @@ def prepare(self, reactor, clock, homeserver): self.tok = self.login("test", "test") self.room_id = self.helper.create_room_as(self.user_id, tok=self.tok) + self.other_user_id = self.register_user("test2", "test2") + self.other_tok = self.login("test2", "test2") + self.helper.invite(self.room_id, self.user_id, self.other_user_id, tok=self.tok) + self.helper.join(self.room_id, self.other_user_id, tok=self.other_tok) + def test_context_filter_labels(self): """Test that we can filter by a label on a /context request.""" res = self._send_labelled_messages_in_room() @@ -1533,6 +1543,124 @@ def test_search_filter_labels_not_labels(self): results[0]["result"]["content"]["body"], ) + def test_edits_labels(self): + """Test that a user can edit the list of labels on a message they sent, + and that no other user than the original event's sender can edit that list of + labels. + """ + event_ids = self._send_labelled_messages_in_room() + + # Check that we can add a label. + res = self.helper.send_event( + room_id=self.room_id, + type=EventTypes.Message, + content={ + "msgtype": "m.text", + "body": "with right label", + EventContentFields.LABELS: ["#fun", "#work"], + "m.relates_to": { + "rel_type": RelationTypes.REPLACE, + "event_id": event_ids[0], + }, + }, + tok=self.tok, + ) + edit_event_id = res["event_id"] + + token = "s0_0_0_0_0_0_0_0_0" + request, channel = self.make_request( + "GET", + "/rooms/%s/messages?access_token=%s&from=%s&filter=%s" + % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS_NOT_LABELS)), + ) + self.render(request) + + chunk = channel.json_body["chunk"] + + event_ids_from_messages = self._event_ids_from_messages_chunk(chunk) + + self.assertTrue( + edit_event_id in event_ids_from_messages, + [event["content"] for event in chunk], + ) + + # Check that we can remove a label. + res = self.helper.send_event( + room_id=self.room_id, + type=EventTypes.Message, + content={ + "msgtype": "m.text", + "body": "with right label", + EventContentFields.LABELS: ["#work"], + "m.relates_to": { + "rel_type": RelationTypes.REPLACE, + "event_id": event_ids[0], + }, + }, + tok=self.tok, + ) + edit_event_id = res["event_id"] + + token = "s0_0_0_0_0_0_0_0_0" + request, channel = self.make_request( + "GET", + "/rooms/%s/messages?access_token=%s&from=%s&filter=%s" + % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS)), + ) + self.render(request) + + chunk = channel.json_body["chunk"] + + event_ids_from_messages = self._event_ids_from_messages_chunk(chunk) + + self.assertTrue( + edit_event_id not in event_ids_from_messages, + [event["content"] for event in chunk], + ) + + # Make sure the original event is filterd out of the response. + self.assertTrue( + event_ids[0] not in event_ids_from_messages, + [event["content"] for event in chunk], + ) + + # Check that only the sender of the original event can edit the labels on that + # event. + res = self.helper.send_event( + room_id=self.room_id, + type=EventTypes.Message, + content={ + "msgtype": "m.text", + "body": "with right label", + EventContentFields.LABELS: ["#fun", "#work"], + "m.relates_to": { + "rel_type": RelationTypes.REPLACE, + "event_id": event_ids[0], + }, + }, + tok=self.tok, + ) + edit_event_id = res["event_id"] + + token = "s0_0_0_0_0_0_0_0_0" + request, channel = self.make_request( + "GET", + "/rooms/%s/messages?access_token=%s&from=%s&filter=%s" + % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS)), + ) + self.render(request) + + self.assertTrue( + edit_event_id not in event_ids_from_messages, + [event["content"] for event in chunk], + ) + + def _event_ids_from_messages_chunk(self, chunk): + event_ids = [] + for event in chunk: + event_ids.append(event.get("event_id")) + return event_ids + def _send_labelled_messages_in_room(self): """Sends several messages to a room with different labels (or without any) to test filtering by label. From 6e25989c59587b6767c84a6411659bfc8ac43b7c Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 6 Dec 2019 11:20:23 +0000 Subject: [PATCH 17/19] Lint --- tests/rest/client/v1/test_rooms.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py index 40637527176a..fb6d202b7a7a 100644 --- a/tests/rest/client/v1/test_rooms.py +++ b/tests/rest/client/v1/test_rooms.py @@ -1571,7 +1571,12 @@ def test_edits_labels(self): request, channel = self.make_request( "GET", "/rooms/%s/messages?access_token=%s&from=%s&filter=%s" - % (self.room_id, self.tok, token, json.dumps(self.FILTER_LABELS_NOT_LABELS)), + % ( + self.room_id, + self.tok, + token, + json.dumps(self.FILTER_LABELS_NOT_LABELS) + ), ) self.render(request) From 83d57ac78b8188ce90bf95dd66971b1a5c4c4c05 Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 6 Dec 2019 11:24:11 +0000 Subject: [PATCH 18/19] Lint --- tests/rest/client/v1/test_rooms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rest/client/v1/test_rooms.py b/tests/rest/client/v1/test_rooms.py index fb6d202b7a7a..458a355132fe 100644 --- a/tests/rest/client/v1/test_rooms.py +++ b/tests/rest/client/v1/test_rooms.py @@ -1575,7 +1575,7 @@ def test_edits_labels(self): self.room_id, self.tok, token, - json.dumps(self.FILTER_LABELS_NOT_LABELS) + json.dumps(self.FILTER_LABELS_NOT_LABELS), ), ) self.render(request) From 3bc68a19a87f5b8ff794a1f7e586902ebd8ecdba Mon Sep 17 00:00:00 2001 From: Brendan Abolivier Date: Fri, 6 Dec 2019 11:43:12 +0000 Subject: [PATCH 19/19] Remove debug select --- synapse/storage/data_stores/main/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/synapse/storage/data_stores/main/events.py b/synapse/storage/data_stores/main/events.py index 06b342ba737c..f4edad30829c 100644 --- a/synapse/storage/data_stores/main/events.py +++ b/synapse/storage/data_stores/main/events.py @@ -2020,7 +2020,6 @@ def insert_labels_for_event_txn( (replaces, replaces), ) - txn.execute("SELECT 1") # We don't care about which row we're using because they'll all have the same # topological ordering, since they're all about the same event. # If the topological ordering (depth) of the current event is lower than the