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

Fix appservices being unable to handle to_device messages for multiple users #16251

Merged
merged 9 commits into from
Sep 5, 2023
1 change: 1 addition & 0 deletions changelog.d/16251.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug where appservices using MSC2409 to receive to_device messages, would only get messages for one user.
2 changes: 1 addition & 1 deletion synapse/storage/databases/main/deviceinbox.py
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def get_device_messages_txn(
table="devices",
column="user_id",
iterable=user_ids_to_query,
keyvalues={"user_id": user_id, "hidden": False},
keyvalues={"hidden": False},
retcols=("device_id",),
)

Expand Down
131 changes: 131 additions & 0 deletions tests/handlers/test_appservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,18 @@ def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
"exclusive_as_user", "password", self.exclusive_as_user_device_id
)

self.exclusive_as_user_2_device_id = "exclusive_as_device_2"
self.exclusive_as_user_2 = self.register_user("exclusive_as_user_2", "password")
self.exclusive_as_user_2_token = self.login(
"exclusive_as_user_2", "password", self.exclusive_as_user_2_device_id
)

self.exclusive_as_user_3_device_id = "exclusive_as_device_3"
self.exclusive_as_user_3 = self.register_user("exclusive_as_user_3", "password")
self.exclusive_as_user_3_token = self.login(
"exclusive_as_user_3", "password", self.exclusive_as_user_3_device_id
)

def _notify_interested_services(self) -> None:
# This is normally set in `notify_interested_services` but we need to call the
# internal async version so the reactor gets pushed to completion.
Expand Down Expand Up @@ -849,6 +861,125 @@ def test_application_services_receive_bursts_of_to_device(self) -> None:
for count in service_id_to_message_count.values():
self.assertEqual(count, number_of_messages)

@unittest.override_config(
{"experimental_features": {"msc2409_to_device_messages_enabled": True}}
)
def test_application_services_receive_local_to_device_for_many_users(self) -> None:
"""
Test that when a user sends a to-device message to many users
in an application service's user namespace, the
application service will receive all of them.
"""
interested_appservice = self._register_application_service(
namespaces={
ApplicationService.NS_USERS: [
{
"regex": "@exclusive_as_user:.+",
"exclusive": True,
},
{
"regex": "@exclusive_as_user_2:.+",
"exclusive": True,
},
{
"regex": "@exclusive_as_user_3:.+",
"exclusive": True,
},
],
},
)

# Have local_user send a to-device message to exclusive_as_users
message_content = {"some_key": "some really interesting value"}
chan = self.make_request(
"PUT",
"/_matrix/client/r0/sendToDevice/m.room_key_request/3",
content={
"messages": {
self.exclusive_as_user: {
self.exclusive_as_user_device_id: message_content
},
self.exclusive_as_user_2: {
self.exclusive_as_user_2_device_id: message_content
},
self.exclusive_as_user_3: {
self.exclusive_as_user_3_device_id: message_content
},
}
},
access_token=self.local_user_token,
)
self.assertEqual(chan.code, 200, chan.result)

# Have exclusive_as_user send a to-device message to local_user
for user_token in [
self.exclusive_as_user_token,
self.exclusive_as_user_2_token,
self.exclusive_as_user_3_token,
]:
chan = self.make_request(
"PUT",
"/_matrix/client/r0/sendToDevice/m.room_key_request/4",
content={
"messages": {
self.local_user: {self.local_user_device_id: message_content}
}
},
access_token=user_token,
)
self.assertEqual(chan.code, 200, chan.result)

# Check if our application service - that is interested in exclusive_as_user - received
# the to-device message as part of an AS transaction.
# Only the local_user -> exclusive_as_user to-device message should have been forwarded to the AS.
#
# The uninterested application service should not have been notified at all.
self.send_mock.assert_called_once()
(
service,
_events,
_ephemeral,
to_device_messages,
_otks,
_fbks,
_device_list_summary,
) = self.send_mock.call_args[0]

# Assert that this was the same to-device message that local_user sent
self.assertEqual(service, interested_appservice)

# Assert expected number of messages
self.assertEqual(len(to_device_messages), 3)

device_msg_1 = {
"msg": to_device_messages[0],
"expected_user": self.exclusive_as_user,
"expected_device": self.exclusive_as_user_device_id,
}

device_msg_2 = {
"msg": to_device_messages[1],
"expected_user": self.exclusive_as_user_2,
"expected_device": self.exclusive_as_user_2_device_id,
}

device_msg_3 = {
"msg": to_device_messages[2],
"expected_user": self.exclusive_as_user_3,
"expected_device": self.exclusive_as_user_3_device_id,
}

for device_msg in [device_msg_1, device_msg_2, device_msg_3]:
self.assertEqual(device_msg["msg"]["type"], "m.room_key_request")
self.assertEqual(device_msg["msg"]["sender"], self.local_user)
self.assertEqual(
device_msg["msg"]["to_user_id"], device_msg["expected_user"]
)
self.assertEqual(
device_msg["msg"]["to_device_id"], device_msg["expected_device"]
)
self.assertEqual(device_msg["msg"]["content"], message_content)
Half-Shot marked this conversation as resolved.
Show resolved Hide resolved

def _register_application_service(
self,
namespaces: Optional[Dict[str, Iterable[Dict]]] = None,
Expand Down
Loading