Skip to content

Commit b548f78

Browse files
Add support for MSC4115 (#17104)
Co-authored-by: Andrew Morgan <[email protected]>
1 parent 758aec6 commit b548f78

File tree

20 files changed

+407
-125
lines changed

20 files changed

+407
-125
lines changed

changelog.d/17104.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for MSC4115 (membership metadata on events).

docker/complement/conf/workers-shared-extra.yaml.j2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ allow_device_name_lookup_over_federation: true
9292
## Experimental Features ##
9393

9494
experimental_features:
95-
# client-side support for partial state in /send_join responses
96-
faster_joins: true
9795
# Enable support for polls
9896
msc3381_polls_enabled: true
9997
# Enable deleting device-specific notification settings stored in account data
@@ -105,6 +103,8 @@ experimental_features:
105103
# no UIA for x-signing upload for the first time
106104
msc3967_enabled: true
107105

106+
msc4115_membership_on_events: true
107+
108108
server_notices:
109109
system_mxid_localpart: _server
110110
system_mxid_display_name: "Server Alert"

rust/src/events/internal_metadata.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020

2121
//! Implements the internal metadata class attached to events.
2222
//!
23-
//! The internal metadata is a bit like a `TypedDict`, in that it is stored as a
24-
//! JSON dict in the DB. Most events have zero, or only a few, of these keys
23+
//! The internal metadata is a bit like a `TypedDict`, in that most of
24+
//! it is stored as a JSON dict in the DB (the exceptions being `outlier`
25+
//! and `stream_ordering` which have their own columns in the database).
26+
//! Most events have zero, or only a few, of these keys
2527
//! set. Therefore, since we care more about memory size than performance here,
2628
//! we store these fields in a mapping.
2729
//!
@@ -234,6 +236,9 @@ impl EventInternalMetadata {
234236
self.clone()
235237
}
236238

239+
/// Get a dict holding the data stored in the `internal_metadata` column in the database.
240+
///
241+
/// Note that `outlier` and `stream_ordering` are stored in separate columns so are not returned here.
237242
fn get_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
238243
let dict = PyDict::new(py);
239244

synapse/api/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,13 @@ class EventContentFields:
234234
TO_DEVICE_MSGID: Final = "org.matrix.msgid"
235235

236236

237+
class EventUnsignedContentFields:
238+
"""Fields found inside the 'unsigned' data on events"""
239+
240+
# Requesting user's membership, per MSC4115
241+
MSC4115_MEMBERSHIP: Final = "io.element.msc4115.membership"
242+
243+
237244
class RoomTypes:
238245
"""Understood values of the room_type field of m.room.create events."""
239246

synapse/config/experimental.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,7 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
432432
"You cannot have MSC4108 both enabled and delegated at the same time",
433433
("experimental", "msc4108_delegation_endpoint"),
434434
)
435+
436+
self.msc4115_membership_on_events = experimental.get(
437+
"msc4115_membership_on_events", False
438+
)

synapse/events/utils.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from synapse.api.room_versions import RoomVersion
5050
from synapse.types import JsonDict, Requester
5151

52-
from . import EventBase
52+
from . import EventBase, make_event_from_dict
5353

5454
if TYPE_CHECKING:
5555
from synapse.handlers.relations import BundledAggregations
@@ -82,17 +82,14 @@ def prune_event(event: EventBase) -> EventBase:
8282
"""
8383
pruned_event_dict = prune_event_dict(event.room_version, event.get_dict())
8484

85-
from . import make_event_from_dict
86-
8785
pruned_event = make_event_from_dict(
8886
pruned_event_dict, event.room_version, event.internal_metadata.get_dict()
8987
)
9088

91-
# copy the internal fields
89+
# Copy the bits of `internal_metadata` that aren't returned by `get_dict`
9290
pruned_event.internal_metadata.stream_ordering = (
9391
event.internal_metadata.stream_ordering
9492
)
95-
9693
pruned_event.internal_metadata.outlier = event.internal_metadata.outlier
9794

9895
# Mark the event as redacted
@@ -101,6 +98,29 @@ def prune_event(event: EventBase) -> EventBase:
10198
return pruned_event
10299

103100

101+
def clone_event(event: EventBase) -> EventBase:
102+
"""Take a copy of the event.
103+
104+
This is mostly useful because it does a *shallow* copy of the `unsigned` data,
105+
which means it can then be updated without corrupting the in-memory cache. Note that
106+
other properties of the event, such as `content`, are *not* (currently) copied here.
107+
"""
108+
# XXX: We rely on at least one of `event.get_dict()` and `make_event_from_dict()`
109+
# making a copy of `unsigned`. Currently, both do, though I don't really know why.
110+
# Still, as long as they do, there's not much point doing yet another copy here.
111+
new_event = make_event_from_dict(
112+
event.get_dict(), event.room_version, event.internal_metadata.get_dict()
113+
)
114+
115+
# Copy the bits of `internal_metadata` that aren't returned by `get_dict`.
116+
new_event.internal_metadata.stream_ordering = (
117+
event.internal_metadata.stream_ordering
118+
)
119+
new_event.internal_metadata.outlier = event.internal_metadata.outlier
120+
121+
return new_event
122+
123+
104124
def prune_event_dict(room_version: RoomVersion, event_dict: JsonDict) -> JsonDict:
105125
"""Redacts the event_dict in the same way as `prune_event`, except it
106126
operates on dicts rather than event objects

synapse/handlers/admin.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(self, hs: "HomeServer"):
4242
self._device_handler = hs.get_device_handler()
4343
self._storage_controllers = hs.get_storage_controllers()
4444
self._state_storage_controller = self._storage_controllers.state
45+
self._hs_config = hs.config
4546
self._msc3866_enabled = hs.config.experimental.msc3866.enabled
4647

4748
async def get_whois(self, user: UserID) -> JsonMapping:
@@ -217,7 +218,10 @@ async def export_user_data(self, user_id: str, writer: "ExfiltrationWriter") ->
217218
)
218219

219220
events = await filter_events_for_client(
220-
self._storage_controllers, user_id, events
221+
self._storage_controllers,
222+
user_id,
223+
events,
224+
msc4115_membership_on_events=self._hs_config.experimental.msc4115_membership_on_events,
221225
)
222226

223227
writer.write_events(room_id, events)

synapse/handlers/events.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class EventHandler:
148148
def __init__(self, hs: "HomeServer"):
149149
self.store = hs.get_datastores().main
150150
self._storage_controllers = hs.get_storage_controllers()
151+
self._config = hs.config
151152

152153
async def get_event(
153154
self,
@@ -189,7 +190,11 @@ async def get_event(
189190
is_peeking = not is_user_in_room
190191

191192
filtered = await filter_events_for_client(
192-
self._storage_controllers, user.to_string(), [event], is_peeking=is_peeking
193+
self._storage_controllers,
194+
user.to_string(),
195+
[event],
196+
is_peeking=is_peeking,
197+
msc4115_membership_on_events=self._config.experimental.msc4115_membership_on_events,
193198
)
194199

195200
if not filtered:

synapse/handlers/initial_sync.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,10 @@ async def handle_room(event: RoomsForUser) -> None:
221221
).addErrback(unwrapFirstError)
222222

223223
messages = await filter_events_for_client(
224-
self._storage_controllers, user_id, messages
224+
self._storage_controllers,
225+
user_id,
226+
messages,
227+
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
225228
)
226229

227230
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)
@@ -380,6 +383,7 @@ async def _room_initial_sync_parted(
380383
requester.user.to_string(),
381384
messages,
382385
is_peeking=is_peeking,
386+
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
383387
)
384388

385389
start_token = StreamToken.START.copy_and_replace(StreamKeyType.ROOM, token)
@@ -494,6 +498,7 @@ async def get_receipts() -> List[JsonMapping]:
494498
requester.user.to_string(),
495499
messages,
496500
is_peeking=is_peeking,
501+
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
497502
)
498503

499504
start_token = now_token.copy_and_replace(StreamKeyType.ROOM, token)

synapse/handlers/pagination.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,7 @@ async def get_messages(
623623
user_id,
624624
events,
625625
is_peeking=(member_event_id is None),
626+
msc4115_membership_on_events=self.hs.config.experimental.msc4115_membership_on_events,
626627
)
627628

628629
# if after the filter applied there are no more events

0 commit comments

Comments
 (0)