Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 4cc4229

Browse files
babolivierrichvdhclokep
authored
Prevent expired events from being filtered out when retention is disabled (#12611)
Co-authored-by: Richard van der Hoff <[email protected]> Co-authored-by: Patrick Cloke <[email protected]>
1 parent a608ac8 commit 4cc4229

File tree

7 files changed

+71
-32
lines changed

7 files changed

+71
-32
lines changed

changelog.d/12611.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug introduced in Synapse 1.7.0 that would prevent events from being sent to clients if there's a retention policy in the room when the support for retention policies is disabled.

synapse/handlers/pagination.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ async def purge_history_for_rooms_in_range(
239239
# defined in the server's configuration, we can safely assume that's the
240240
# case and use it for this room.
241241
max_lifetime = (
242-
retention_policy["max_lifetime"] or self._retention_default_max_lifetime
242+
retention_policy.max_lifetime or self._retention_default_max_lifetime
243243
)
244244

245245
# Cap the effective max_lifetime to be within the range allowed in the

synapse/storage/databases/main/room.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from synapse.storage.databases.main.cache import CacheInvalidationWorkerStore
4646
from synapse.storage.types import Cursor
4747
from synapse.storage.util.id_generators import IdGenerator
48-
from synapse.types import JsonDict, ThirdPartyInstanceID
48+
from synapse.types import JsonDict, RetentionPolicy, ThirdPartyInstanceID
4949
from synapse.util import json_encoder
5050
from synapse.util.caches.descriptors import cached
5151
from synapse.util.stringutils import MXC_REGEX
@@ -699,20 +699,28 @@ def delete_ratelimit_txn(txn: LoggingTransaction) -> None:
699699
await self.db_pool.runInteraction("delete_ratelimit", delete_ratelimit_txn)
700700

701701
@cached()
702-
async def get_retention_policy_for_room(self, room_id: str) -> Dict[str, int]:
702+
async def get_retention_policy_for_room(self, room_id: str) -> RetentionPolicy:
703703
"""Get the retention policy for a given room.
704704
705705
If no retention policy has been found for this room, returns a policy defined
706706
by the configured default policy (which has None as both the 'min_lifetime' and
707707
the 'max_lifetime' if no default policy has been defined in the server's
708708
configuration).
709709
710+
If support for retention policies is disabled, a policy with a 'min_lifetime' and
711+
'max_lifetime' of None is returned.
712+
710713
Args:
711714
room_id: The ID of the room to get the retention policy of.
712715
713716
Returns:
714717
A dict containing "min_lifetime" and "max_lifetime" for this room.
715718
"""
719+
# If the room retention feature is disabled, return a policy with no minimum nor
720+
# maximum. This prevents incorrectly filtering out events when sending to
721+
# the client.
722+
if not self.config.retention.retention_enabled:
723+
return RetentionPolicy()
716724

717725
def get_retention_policy_for_room_txn(
718726
txn: LoggingTransaction,
@@ -736,10 +744,10 @@ def get_retention_policy_for_room_txn(
736744
# If we don't know this room ID, ret will be None, in this case return the default
737745
# policy.
738746
if not ret:
739-
return {
740-
"min_lifetime": self.config.retention.retention_default_min_lifetime,
741-
"max_lifetime": self.config.retention.retention_default_max_lifetime,
742-
}
747+
return RetentionPolicy(
748+
min_lifetime=self.config.retention.retention_default_min_lifetime,
749+
max_lifetime=self.config.retention.retention_default_max_lifetime,
750+
)
743751

744752
min_lifetime = ret[0]["min_lifetime"]
745753
max_lifetime = ret[0]["max_lifetime"]
@@ -754,10 +762,10 @@ def get_retention_policy_for_room_txn(
754762
if max_lifetime is None:
755763
max_lifetime = self.config.retention.retention_default_max_lifetime
756764

757-
return {
758-
"min_lifetime": min_lifetime,
759-
"max_lifetime": max_lifetime,
760-
}
765+
return RetentionPolicy(
766+
min_lifetime=min_lifetime,
767+
max_lifetime=max_lifetime,
768+
)
761769

762770
async def get_media_mxcs_in_room(self, room_id: str) -> Tuple[List[str], List[str]]:
763771
"""Retrieves all the local and remote media MXC URIs in a given room
@@ -994,7 +1002,7 @@ def _quarantine_media_txn(
9941002

9951003
async def get_rooms_for_retention_period_in_range(
9961004
self, min_ms: Optional[int], max_ms: Optional[int], include_null: bool = False
997-
) -> Dict[str, Dict[str, Optional[int]]]:
1005+
) -> Dict[str, RetentionPolicy]:
9981006
"""Retrieves all of the rooms within the given retention range.
9991007
10001008
Optionally includes the rooms which don't have a retention policy.
@@ -1016,7 +1024,7 @@ async def get_rooms_for_retention_period_in_range(
10161024

10171025
def get_rooms_for_retention_period_in_range_txn(
10181026
txn: LoggingTransaction,
1019-
) -> Dict[str, Dict[str, Optional[int]]]:
1027+
) -> Dict[str, RetentionPolicy]:
10201028
range_conditions = []
10211029
args = []
10221030

@@ -1047,10 +1055,10 @@ def get_rooms_for_retention_period_in_range_txn(
10471055
rooms_dict = {}
10481056

10491057
for row in rows:
1050-
rooms_dict[row["room_id"]] = {
1051-
"min_lifetime": row["min_lifetime"],
1052-
"max_lifetime": row["max_lifetime"],
1053-
}
1058+
rooms_dict[row["room_id"]] = RetentionPolicy(
1059+
min_lifetime=row["min_lifetime"],
1060+
max_lifetime=row["max_lifetime"],
1061+
)
10541062

10551063
if include_null:
10561064
# If required, do a second query that retrieves all of the rooms we know
@@ -1065,10 +1073,7 @@ def get_rooms_for_retention_period_in_range_txn(
10651073
# policy in its state), add it with a null policy.
10661074
for row in rows:
10671075
if row["room_id"] not in rooms_dict:
1068-
rooms_dict[row["room_id"]] = {
1069-
"min_lifetime": None,
1070-
"max_lifetime": None,
1071-
}
1076+
rooms_dict[row["room_id"]] = RetentionPolicy()
10721077

10731078
return rooms_dict
10741079

synapse/types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,3 +932,9 @@ class UserProfile(TypedDict):
932932
user_id: str
933933
display_name: Optional[str]
934934
avatar_url: Optional[str]
935+
936+
937+
@attr.s(auto_attribs=True, frozen=True, slots=True)
938+
class RetentionPolicy:
939+
min_lifetime: Optional[int] = None
940+
max_lifetime: Optional[int] = None

synapse/visibility.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from synapse.events.utils import prune_event
2323
from synapse.storage import Storage
2424
from synapse.storage.state import StateFilter
25-
from synapse.types import StateMap, get_domain_from_id
25+
from synapse.types import RetentionPolicy, StateMap, get_domain_from_id
2626

2727
logger = logging.getLogger(__name__)
2828

@@ -94,7 +94,7 @@ async def filter_events_for_client(
9494

9595
if filter_send_to_client:
9696
room_ids = {e.room_id for e in events}
97-
retention_policies = {}
97+
retention_policies: Dict[str, RetentionPolicy] = {}
9898

9999
for room_id in room_ids:
100100
retention_policies[
@@ -137,7 +137,7 @@ def allowed(event: EventBase) -> Optional[EventBase]:
137137
# events.
138138
if not event.is_state():
139139
retention_policy = retention_policies[event.room_id]
140-
max_lifetime = retention_policy.get("max_lifetime")
140+
max_lifetime = retention_policy.max_lifetime
141141

142142
if max_lifetime is not None:
143143
oldest_allowed_ts = storage.main.clock.time_msec() - max_lifetime

tests/rest/client/test_relations.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ def assert_annotations(bundled_aggregations: JsonDict) -> None:
995995
bundled_aggregations,
996996
)
997997

998-
self._test_bundled_aggregations(RelationTypes.ANNOTATION, assert_annotations, 7)
998+
self._test_bundled_aggregations(RelationTypes.ANNOTATION, assert_annotations, 6)
999999

10001000
def test_annotation_to_annotation(self) -> None:
10011001
"""Any relation to an annotation should be ignored."""
@@ -1031,7 +1031,7 @@ def assert_annotations(bundled_aggregations: JsonDict) -> None:
10311031
bundled_aggregations,
10321032
)
10331033

1034-
self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 7)
1034+
self._test_bundled_aggregations(RelationTypes.REFERENCE, assert_annotations, 6)
10351035

10361036
def test_thread(self) -> None:
10371037
"""
@@ -1060,7 +1060,7 @@ def assert_thread(bundled_aggregations: JsonDict) -> None:
10601060
bundled_aggregations.get("latest_event"),
10611061
)
10621062

1063-
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 10)
1063+
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 9)
10641064

10651065
def test_thread_with_bundled_aggregations_for_latest(self) -> None:
10661066
"""
@@ -1106,7 +1106,7 @@ def assert_thread(bundled_aggregations: JsonDict) -> None:
11061106
bundled_aggregations["latest_event"].get("unsigned"),
11071107
)
11081108

1109-
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 10)
1109+
self._test_bundled_aggregations(RelationTypes.THREAD, assert_thread, 9)
11101110

11111111
def test_nested_thread(self) -> None:
11121112
"""

tests/rest/client/test_retention.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
from typing import Any, Dict
1415
from unittest.mock import Mock
1516

1617
from twisted.test.proto_helpers import MemoryReactor
@@ -252,16 +253,24 @@ class RetentionNoDefaultPolicyTestCase(unittest.HomeserverTestCase):
252253
room.register_servlets,
253254
]
254255

255-
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
256-
config = self.default_config()
257-
config["retention"] = {
256+
def default_config(self) -> Dict[str, Any]:
257+
config = super().default_config()
258+
259+
retention_config = {
258260
"enabled": True,
259261
}
260262

263+
# Update this config with what's in the default config so that
264+
# override_config works as expected.
265+
retention_config.update(config.get("retention", {}))
266+
config["retention"] = retention_config
267+
268+
return config
269+
270+
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
261271
mock_federation_client = Mock(spec=["backfill"])
262272

263273
self.hs = self.setup_test_homeserver(
264-
config=config,
265274
federation_client=mock_federation_client,
266275
)
267276
return self.hs
@@ -295,6 +304,24 @@ def test_state_policy(self) -> None:
295304

296305
self._test_retention(room_id, expected_code_for_first_event=404)
297306

307+
@unittest.override_config({"retention": {"enabled": False}})
308+
def test_visibility_when_disabled(self) -> None:
309+
"""Retention policies should be ignored when the retention feature is disabled."""
310+
room_id = self.helper.create_room_as(self.user_id, tok=self.token)
311+
312+
self.helper.send_state(
313+
room_id=room_id,
314+
event_type=EventTypes.Retention,
315+
body={"max_lifetime": one_day_ms},
316+
tok=self.token,
317+
)
318+
319+
resp = self.helper.send(room_id=room_id, body="test", tok=self.token)
320+
321+
self.reactor.advance(one_day_ms * 2 / 1000)
322+
323+
self.get_event(room_id, resp["event_id"])
324+
298325
def _test_retention(
299326
self, room_id: str, expected_code_for_first_event: int = 200
300327
) -> None:

0 commit comments

Comments
 (0)