Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.
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/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
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
125 changes: 125 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,119 @@ 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)

for device_msg in to_device_messages:
self.assertEqual(device_msg["type"], "m.room_key_request")
self.assertEqual(device_msg["sender"], self.local_user)
self.assertEqual(device_msg["content"], message_content)

self.assertEqual(to_device_messages[0]["to_user_id"], self.exclusive_as_user)
self.assertEqual(
to_device_messages[0]["to_device_id"],
self.exclusive_as_user_device_id,
)

self.assertEqual(to_device_messages[1]["to_user_id"], self.exclusive_as_user_2)
self.assertEqual(
to_device_messages[1]["to_device_id"],
self.exclusive_as_user_2_device_id,
)

self.assertEqual(to_device_messages[2]["to_user_id"], self.exclusive_as_user_3)
self.assertEqual(
to_device_messages[2]["to_device_id"],
self.exclusive_as_user_3_device_id,
)

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