From d1c73e71c70dde8ab366a359621c4cb23db48e40 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 13 Mar 2025 15:01:15 -0600 Subject: [PATCH 1/5] Allow admins to see soft failed events --- synapse/visibility.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/synapse/visibility.py b/synapse/visibility.py index dc7b6e4065..d064950eb5 100644 --- a/synapse/visibility.py +++ b/synapse/visibility.py @@ -48,7 +48,7 @@ from synapse.logging.opentracing import trace from synapse.storage.controllers import StorageControllers from synapse.storage.databases.main import DataStore from synapse.synapse_rust.events import event_visible_to_server -from synapse.types import RetentionPolicy, StateMap, StrCollection, get_domain_from_id +from synapse.types import RetentionPolicy, StateMap, StrCollection, get_domain_from_id, UserID from synapse.types.state import StateFilter from synapse.util import Clock @@ -106,9 +106,12 @@ async def filter_events_for_client( of `user_id` at each event. """ # Filter out events that have been soft failed so that we don't relay them - # to clients. + # to clients, unless they're a server admin. events_before_filtering = events - events = [e for e in events if not e.internal_metadata.is_soft_failed()] + if filter_send_to_client and await storage.main.is_server_admin(UserID.from_string(user_id)): + events = events_before_filtering + else: + events = [e for e in events if not e.internal_metadata.is_soft_failed()] if len(events_before_filtering) != len(events): if filtered_event_logger.isEnabledFor(logging.DEBUG): filtered_event_logger.debug( From a855b55c6bbc4e710dad0d40d2360e10de4c4b74 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 13 Mar 2025 15:05:19 -0600 Subject: [PATCH 2/5] changelog --- changelog.d/18238.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/18238.feature diff --git a/changelog.d/18238.feature b/changelog.d/18238.feature new file mode 100644 index 0000000000..d89f273fbe --- /dev/null +++ b/changelog.d/18238.feature @@ -0,0 +1 @@ +Server admins will see [soft failed](https://spec.matrix.org/v1.13/server-server-api/#soft-failure) events over the Client-Server API. \ No newline at end of file From 8f2fa30fcbc2a5508268b899631923abbee42424 Mon Sep 17 00:00:00 2001 From: turt2live <1190097+turt2live@users.noreply.github.com> Date: Thu, 13 Mar 2025 21:07:15 +0000 Subject: [PATCH 3/5] Attempt to fix linting --- synapse/visibility.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/synapse/visibility.py b/synapse/visibility.py index d064950eb5..78d33f278b 100644 --- a/synapse/visibility.py +++ b/synapse/visibility.py @@ -48,7 +48,13 @@ from synapse.logging.opentracing import trace from synapse.storage.controllers import StorageControllers from synapse.storage.databases.main import DataStore from synapse.synapse_rust.events import event_visible_to_server -from synapse.types import RetentionPolicy, StateMap, StrCollection, get_domain_from_id, UserID +from synapse.types import ( + RetentionPolicy, + StateMap, + StrCollection, + UserID, + get_domain_from_id, +) from synapse.types.state import StateFilter from synapse.util import Clock @@ -108,7 +114,9 @@ async def filter_events_for_client( # Filter out events that have been soft failed so that we don't relay them # to clients, unless they're a server admin. events_before_filtering = events - if filter_send_to_client and await storage.main.is_server_admin(UserID.from_string(user_id)): + if filter_send_to_client and await storage.main.is_server_admin( + UserID.from_string(user_id) + ): events = events_before_filtering else: events = [e for e in events if not e.internal_metadata.is_soft_failed()] From 331bc7c0fd489ea15c7ffa23fcbf8606b1b0c745 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 13 Mar 2025 15:08:57 -0600 Subject: [PATCH 4/5] Empty commit to fix CI From b453b1a7ddccb10145bd89083979c96e72c9a4f0 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 14 Mar 2025 09:58:41 +0000 Subject: [PATCH 5/5] Bump db txn expected count in relations tests As we're now performing another db txn to check if the user is an admin. --- tests/rest/client/test_relations.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/rest/client/test_relations.py b/tests/rest/client/test_relations.py index f5a7602d0a..8f2f44739c 100644 --- a/tests/rest/client/test_relations.py +++ b/tests/rest/client/test_relations.py @@ -1181,7 +1181,7 @@ class BundledAggregationsTestCase(BaseRelationsTestCase): bundled_aggregations, ) - self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 6) + self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 7) def test_thread(self) -> None: """ @@ -1226,21 +1226,21 @@ class BundledAggregationsTestCase(BaseRelationsTestCase): # The "user" sent the root event and is making queries for the bundled # aggregations: they have participated. - self._test_bundled_aggregations(RelationTypes.THREAD, _gen_assert(True), 6) + self._test_bundled_aggregations(RelationTypes.THREAD, _gen_assert(True), 7) # The "user2" sent replies in the thread and is making queries for the # bundled aggregations: they have participated. # # Note that this re-uses some cached values, so the total number of # queries is much smaller. self._test_bundled_aggregations( - RelationTypes.THREAD, _gen_assert(True), 3, access_token=self.user2_token + RelationTypes.THREAD, _gen_assert(True), 4, access_token=self.user2_token ) # A user with no interactions with the thread: they have not participated. user3_id, user3_token = self._create_user("charlie") self.helper.join(self.room, user=user3_id, tok=user3_token) self._test_bundled_aggregations( - RelationTypes.THREAD, _gen_assert(False), 3, access_token=user3_token + RelationTypes.THREAD, _gen_assert(False), 4, access_token=user3_token ) def test_thread_with_bundled_aggregations_for_latest(self) -> None: @@ -1287,7 +1287,7 @@ class BundledAggregationsTestCase(BaseRelationsTestCase): bundled_aggregations["latest_event"].get("unsigned"), ) - self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 6) + self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 7) def test_nested_thread(self) -> None: """