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

Update client-visibility filtering for outlier events #12155

Merged
merged 1 commit into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/12155.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Avoid trying to calculate the state at outlier events.
17 changes: 16 additions & 1 deletion synapse/visibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ async def filter_events_for_client(

types = ((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member, user_id))

# we exclude outliers at this point, and then handle them separately later
event_id_to_state = await storage.state.get_state_for_events(
frozenset(e.event_id for e in events),
frozenset(e.event_id for e in events if not e.internal_metadata.outlier),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's worth noting that currently, get_state_for_events returns an empty list when called on an outlier (which is why this currently works).

However, I'm planning to change it to raise an exception (because I'm pretty sure that doing so is normally a programming error and produces mess like #12074), which is why this PR is needed.

state_filter=StateFilter.from_types(types),
)

Expand Down Expand Up @@ -154,6 +155,17 @@ def allowed(event: EventBase) -> Optional[EventBase]:
if event.event_id in always_include_ids:
return event

# we need to handle outliers separately, since we don't have the room state.
if event.internal_metadata.outlier:
# Normally these can't be seen by clients, but we make an exception for
# for out-of-band membership events (eg, incoming invites, or rejections of
# said invite) for the user themselves.
if event.type == EventTypes.Member and event.state_key == user_id:
logger.debug("Returning out-of-band-membership event %s", event)
return event

return None

state = event_id_to_state[event.event_id]

# get the room_visibility at the time of the event.
Expand Down Expand Up @@ -198,6 +210,9 @@ def allowed(event: EventBase) -> Optional[EventBase]:

# Always allow the user to see their own leave events, otherwise
# they won't see the room disappear if they reject the invite
#
# (Note this doesn't work for out-of-band invite rejections, which don't
# have prev_state populated. They are handled above in the outlier code.)
if membership == "leave" and (
prev_membership == "join" or prev_membership == "invite"
):
Expand Down
76 changes: 73 additions & 3 deletions tests/test_visibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
# limitations under the License.
import logging
from typing import Optional
from unittest.mock import patch

from synapse.api.room_versions import RoomVersions
from synapse.events import EventBase
from synapse.types import JsonDict
from synapse.visibility import filter_events_for_server
from synapse.events import EventBase, make_event_from_dict
from synapse.types import JsonDict, create_requester
from synapse.visibility import filter_events_for_client, filter_events_for_server

from tests import unittest
from tests.utils import create_room
Expand Down Expand Up @@ -185,3 +186,72 @@ def _inject_message(

self.get_success(self.storage.persistence.persist_event(event, context))
return event


class FilterEventsForClientTestCase(unittest.FederatingHomeserverTestCase):
def test_out_of_band_invite_rejection(self):
# this is where we have received an invite event over federation, and then
# rejected it.
invite_pdu = {
"room_id": "!room:id",
"depth": 1,
"auth_events": [],
"prev_events": [],
"origin_server_ts": 1,
"sender": "@someone:" + self.OTHER_SERVER_NAME,
"type": "m.room.member",
"state_key": "@user:test",
"content": {"membership": "invite"},
}
self.add_hashes_and_signatures(invite_pdu)
invite_event_id = make_event_from_dict(invite_pdu, RoomVersions.V9).event_id

self.get_success(
self.hs.get_federation_server().on_invite_request(
self.OTHER_SERVER_NAME,
invite_pdu,
"9",
)
)

# stub out do_remotely_reject_invite so that we fall back to a locally-
# generated rejection
with patch.object(
self.hs.get_federation_handler(),
"do_remotely_reject_invite",
side_effect=Exception(),
):
reject_event_id, _ = self.get_success(
self.hs.get_room_member_handler().remote_reject_invite(
invite_event_id,
txn_id=None,
requester=create_requester("@user:test"),
content={},
)
)

invite_event, reject_event = self.get_success(
self.hs.get_datastores().main.get_events_as_list(
[invite_event_id, reject_event_id]
)
)

# the invited user should be able to see both the invite and the rejection
self.assertEqual(
self.get_success(
filter_events_for_client(
self.hs.get_storage(), "@user:test", [invite_event, reject_event]
)
),
[invite_event, reject_event],
)

# other users should see neither
self.assertEqual(
self.get_success(
filter_events_for_client(
self.hs.get_storage(), "@other:test", [invite_event, reject_event]
)
),
[],
)