mirror of
https://github.com/element-hq/synapse.git
synced 2026-11-04 19:57:45 +00:00
Merge #20450: Allow loading the synthetic join of a Server Announcement room with /context
This commit is contained in:
4 files changed
+137
-12
No files matched your search
@@ -75,6 +75,10 @@ from synapse.events.utils import (
|
||||
copy_and_fixup_power_levels_contents,
|
||||
)
|
||||
from synapse.handlers.relations import BundledAggregations
|
||||
from synapse.handlers.server_announcements import (
|
||||
make_synthetic_join_event,
|
||||
make_synthetic_join_event_id,
|
||||
)
|
||||
from synapse.rest.admin._base import assert_user_is_admin
|
||||
from synapse.streams import EventSource
|
||||
from synapse.types import (
|
||||
@@ -122,6 +126,7 @@ class EventContext:
|
||||
aggregations: dict[str, BundledAggregations]
|
||||
start: str
|
||||
end: str
|
||||
synthetic_event: JsonDict | None = None
|
||||
|
||||
|
||||
class RoomCreationHandler:
|
||||
@@ -2023,6 +2028,10 @@ class RoomContextHandler:
|
||||
"""Retrieves events, pagination tokens and state around a given event
|
||||
in a room.
|
||||
|
||||
The requester's synthetic join in an announcement room is never persisted.
|
||||
It is returned as the `synthetic_event` of the result, at the position of
|
||||
the room's create event, which is not itself returned to the requester.
|
||||
|
||||
Args:
|
||||
requester
|
||||
room_id
|
||||
@@ -2060,13 +2069,35 @@ class RoomContextHandler:
|
||||
is_peeking=is_peeking,
|
||||
)
|
||||
|
||||
# When requesting the context of a synthetic announcement room membership
|
||||
# the context displayed will be based on the create event
|
||||
synthetic_join = None
|
||||
if (
|
||||
not use_admin_priviledge
|
||||
and event_id == make_synthetic_join_event_id(room_id, user.to_string())
|
||||
and await self.store.get_server_announcement_id(room_id) is not None
|
||||
):
|
||||
create_event = await self.store.get_create_event_for_room(room_id)
|
||||
synthetic_join = make_synthetic_join_event(
|
||||
room_id, user.to_string(), create_event.origin_server_ts
|
||||
)
|
||||
event_id = create_event.event_id
|
||||
|
||||
event = await self.store.get_event(
|
||||
event_id, get_prev_content=True, allow_none=True
|
||||
)
|
||||
if not event:
|
||||
return None
|
||||
|
||||
filtered = await filter_evts([event])
|
||||
if synthetic_join is not None:
|
||||
# Here the `event` is the create event of the room. A user peeking at the
|
||||
# room cannot pass filter_evts() on it, since the history_visibility is set
|
||||
# after the create event. This is fine as the event is not returned to the
|
||||
# requester.
|
||||
filtered = [FilteredEvent.state(event)]
|
||||
else:
|
||||
filtered = await filter_evts([event])
|
||||
|
||||
if not filtered:
|
||||
raise AuthError(403, "You don't have permission to access that event.")
|
||||
|
||||
@@ -2140,6 +2171,7 @@ class RoomContextHandler:
|
||||
end=await token.copy_and_replace(StreamKeyType.ROOM, results.end).to_string(
|
||||
self.store
|
||||
),
|
||||
synthetic_event=synthetic_join,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -43,9 +43,14 @@ anything in yet is not listed.
|
||||
|
||||
|
||||
def make_synthetic_join_event_id(room_id: str, user_id: str) -> str:
|
||||
"""Return the event ID of the synthetic join of `user_id` in `room_id`.
|
||||
"""Build the event ID of the synthetic join of a user in an announcement room.
|
||||
|
||||
See `make_synthetic_join_event`.
|
||||
Args:
|
||||
room_id: The announcement room.
|
||||
user_id: The user receiving the event.
|
||||
|
||||
Returns:
|
||||
The event ID, which is the same every time for a given room and user.
|
||||
"""
|
||||
return "-".join(
|
||||
[
|
||||
|
||||
@@ -1280,6 +1280,19 @@ class RoomEventContextServlet(RestServlet):
|
||||
serializer_options = await self._event_serializer.create_config(
|
||||
requester=requester
|
||||
)
|
||||
|
||||
if event_context.synthetic_event is not None:
|
||||
event = self._event_serializer.serialize_synthetic_event(
|
||||
event_context.synthetic_event, serializer_options
|
||||
)
|
||||
else:
|
||||
event = await self._event_serializer.serialize_event(
|
||||
event_context.event,
|
||||
time_now,
|
||||
bundle_aggregations=event_context.aggregations,
|
||||
config=serializer_options,
|
||||
)
|
||||
|
||||
results = {
|
||||
"events_before": await self._event_serializer.serialize_events(
|
||||
event_context.events_before,
|
||||
@@ -1287,12 +1300,7 @@ class RoomEventContextServlet(RestServlet):
|
||||
bundle_aggregations=event_context.aggregations,
|
||||
config=serializer_options,
|
||||
),
|
||||
"event": await self._event_serializer.serialize_event(
|
||||
event_context.event,
|
||||
time_now,
|
||||
bundle_aggregations=event_context.aggregations,
|
||||
config=serializer_options,
|
||||
),
|
||||
"event": event,
|
||||
"events_after": await self._event_serializer.serialize_events(
|
||||
event_context.events_after,
|
||||
time_now,
|
||||
|
||||
@@ -18,7 +18,8 @@ from twisted.internet.testing import MemoryReactor
|
||||
import synapse.rest.admin
|
||||
from synapse.api.constants import EventTypes, RelationTypes
|
||||
from synapse.config.server_announcements import ServerAnnouncementRoom
|
||||
from synapse.rest.client import login, relations, room
|
||||
from synapse.handlers.server_announcements import make_synthetic_join_event_id
|
||||
from synapse.rest.client import login, relations, room, sync
|
||||
from synapse.server import HomeServer
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.clock import Clock
|
||||
@@ -52,12 +53,13 @@ class ServerAnnouncementsHistoryTestCase(unittest.HomeserverTestCase):
|
||||
room.register_servlets,
|
||||
room.register_deprecated_servlets,
|
||||
relations.register_servlets,
|
||||
sync.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.announcer = self.register_user("announcer", "pass")
|
||||
self.announcer_tok = self.login("announcer", "pass")
|
||||
reader = self.register_user("reader", "pass")
|
||||
self.reader = self.register_user("reader", "pass")
|
||||
self.reader_tok = self.login("reader", "pass")
|
||||
|
||||
room_config = ServerAnnouncementRoom(
|
||||
@@ -82,7 +84,7 @@ class ServerAnnouncementsHistoryTestCase(unittest.HomeserverTestCase):
|
||||
room_topic=room_config.room_topic,
|
||||
room_avatar_url=room_config.room_avatar_url,
|
||||
room_avatar_path=room_config.room_avatar_path,
|
||||
allowed_user_ids=[self.announcer, reader],
|
||||
allowed_user_ids=[self.announcer, self.reader],
|
||||
)
|
||||
]
|
||||
self.get_success(handler.reconcile_rooms())
|
||||
@@ -135,6 +137,84 @@ class ServerAnnouncementsHistoryTestCase(unittest.HomeserverTestCase):
|
||||
body = self._get(f"/rooms/{self.room_id}/context/{self.announcement_id}")
|
||||
self.assertEqual(body["event"]["event_id"], self.announcement_id)
|
||||
|
||||
def _get_synthetic_join(self) -> JsonDict:
|
||||
"""Get the reader's synthetic join from `/sync`."""
|
||||
room = self._get("/sync")["rooms"]["join"][self.room_id]
|
||||
return next(
|
||||
event
|
||||
for event in room["state"]["events"]
|
||||
if event["type"] == EventTypes.Member and event["state_key"] == self.reader
|
||||
)
|
||||
|
||||
def test_context_of_synthetic_join(self) -> None:
|
||||
"""The reader can load the synthetic join they received in `/sync`."""
|
||||
join = self._get_synthetic_join()
|
||||
|
||||
body = self._get(f"/rooms/{self.room_id}/context/{join['event_id']}")
|
||||
self.assertEqual(
|
||||
body["event"], {**join, "room_id": self.room_id, "user_id": self.reader}
|
||||
)
|
||||
|
||||
def test_context_of_synthetic_join_forward_pagination(self) -> None:
|
||||
"""The reader can paginate forwards from the synthetic join to the
|
||||
announcement."""
|
||||
join = self._get_synthetic_join()
|
||||
|
||||
context = self._get(f"/rooms/{self.room_id}/context/{join['event_id']}")
|
||||
body = self._get(
|
||||
f"/rooms/{self.room_id}/messages?dir=f&limit=100&from={context['end']}"
|
||||
)
|
||||
self.assertIn(
|
||||
self.announcement_id, [event["event_id"] for event in body["chunk"]]
|
||||
)
|
||||
|
||||
def test_context_of_synthetic_join_surrounding_events(self) -> None:
|
||||
"""The synthetic join comes first in the room, followed by the events the
|
||||
reader can see."""
|
||||
join = self._get_synthetic_join()
|
||||
|
||||
body = self._get(f"/rooms/{self.room_id}/context/{join['event_id']}?limit=100")
|
||||
self.assertEqual(body["events_before"], [])
|
||||
self.assertIn(
|
||||
self.announcement_id, [event["event_id"] for event in body["events_after"]]
|
||||
)
|
||||
|
||||
def test_context_of_synthetic_join_of_another_user(self) -> None:
|
||||
"""The reader cannot load the synthetic join of another user."""
|
||||
event_id = make_synthetic_join_event_id(self.room_id, self.announcer)
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/rooms/{self.room_id}/context/{event_id}",
|
||||
access_token=self.reader_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 404, channel.json_body)
|
||||
|
||||
def test_context_of_synthetic_join_after_promotion(self) -> None:
|
||||
"""The reader can still load the synthetic join they received in `/sync`
|
||||
after a configuration change makes them a sender, and so a member."""
|
||||
join = self._get_synthetic_join()
|
||||
|
||||
config = self.hs.config.serverannouncements
|
||||
room_config = config.rooms[0]
|
||||
config.rooms = [
|
||||
ServerAnnouncementRoom(
|
||||
config_id=room_config.config_id,
|
||||
room_name=room_config.room_name,
|
||||
room_topic=room_config.room_topic,
|
||||
room_avatar_url=room_config.room_avatar_url,
|
||||
room_avatar_path=room_config.room_avatar_path,
|
||||
allowed_user_ids=[self.announcer, self.reader],
|
||||
)
|
||||
]
|
||||
self.get_success(self.hs.get_server_announcements_handler().reconcile_rooms())
|
||||
self.assertIn(self.room_id, self._get("/joined_rooms")["joined_rooms"])
|
||||
|
||||
body = self._get(f"/rooms/{self.room_id}/context/{join['event_id']}")
|
||||
self.assertEqual(
|
||||
body["event"], {**join, "room_id": self.room_id, "user_id": self.reader}
|
||||
)
|
||||
|
||||
def test_event(self) -> None:
|
||||
"""The reader can fetch the announcement by its ID."""
|
||||
body = self._get(f"/rooms/{self.room_id}/event/{self.announcement_id}")
|
||||
|
||||
Reference in new issue
Block a user