diff --git a/changelog.d/11909.misc b/changelog.d/11909.misc
new file mode 100644
index 0000000000..ffd3e5c639
--- /dev/null
+++ b/changelog.d/11909.misc
@@ -0,0 +1 @@
+Add a test that checks users receive their own device list updates down `/sync`.
\ No newline at end of file
diff --git a/changelog.d/11911.misc b/changelog.d/11911.misc
new file mode 100644
index 0000000000..805588c2e9
--- /dev/null
+++ b/changelog.d/11911.misc
@@ -0,0 +1 @@
+Various refactors to the application service notifier code.
\ No newline at end of file
diff --git a/changelog.d/11914.misc b/changelog.d/11914.misc
new file mode 100644
index 0000000000..c288d43455
--- /dev/null
+++ b/changelog.d/11914.misc
@@ -0,0 +1 @@
+Various refactors to the typing notifications code.
\ No newline at end of file
diff --git a/changelog.d/11927.misc b/changelog.d/11927.misc
new file mode 100644
index 0000000000..22c58521c9
--- /dev/null
+++ b/changelog.d/11927.misc
@@ -0,0 +1 @@
+Use the proper type for the Content-Length header in the `UploadResource`.
diff --git a/synapse/appservice/__init__.py b/synapse/appservice/__init__.py
index 7dbebd97b5..a340a8c9c7 100644
--- a/synapse/appservice/__init__.py
+++ b/synapse/appservice/__init__.py
@@ -165,23 +165,16 @@ class ApplicationService:
return namespace.exclusive
return False
- async def _matches_user(
- self, event: Optional[EventBase], store: Optional["DataStore"] = None
- ) -> bool:
- if not event:
- return False
-
+ async def _matches_user(self, event: EventBase, store: "DataStore") -> bool:
if self.is_interested_in_user(event.sender):
return True
+
# also check m.room.member state key
if event.type == EventTypes.Member and self.is_interested_in_user(
event.state_key
):
return True
- if not store:
- return False
-
does_match = await self.matches_user_in_member_list(event.room_id, store)
return does_match
@@ -216,21 +209,15 @@ class ApplicationService:
return self.is_interested_in_room(event.room_id)
return False
- async def _matches_aliases(
- self, event: EventBase, store: Optional["DataStore"] = None
- ) -> bool:
- if not store or not event:
- return False
-
+ async def _matches_aliases(self, event: EventBase, store: "DataStore") -> bool:
alias_list = await store.get_aliases_for_room(event.room_id)
for alias in alias_list:
if self.is_interested_in_alias(alias):
return True
+
return False
- async def is_interested(
- self, event: EventBase, store: Optional["DataStore"] = None
- ) -> bool:
+ async def is_interested(self, event: EventBase, store: "DataStore") -> bool:
"""Check if this service is interested in this event.
Args:
diff --git a/synapse/handlers/appservice.py b/synapse/handlers/appservice.py
index 0fb919acf6..a42c3558e4 100644
--- a/synapse/handlers/appservice.py
+++ b/synapse/handlers/appservice.py
@@ -649,7 +649,7 @@ class ApplicationServicesHandler:
"""Retrieve a list of application services interested in this event.
Args:
- event: The event to check. Can be None if alias_list is not.
+ event: The event to check.
Returns:
A list of services interested in this event based on the service regex.
"""
diff --git a/synapse/handlers/typing.py b/synapse/handlers/typing.py
index e43c22832d..e4bed1c937 100644
--- a/synapse/handlers/typing.py
+++ b/synapse/handlers/typing.py
@@ -446,7 +446,7 @@ class TypingWriterHandler(FollowerTypingHandler):
class TypingNotificationEventSource(EventSource[int, JsonDict]):
def __init__(self, hs: "HomeServer"):
- self.hs = hs
+ self._main_store = hs.get_datastore()
self.clock = hs.get_clock()
# We can't call get_typing_handler here because there's a cycle:
#
@@ -487,7 +487,7 @@ class TypingNotificationEventSource(EventSource[int, JsonDict]):
continue
if not await service.matches_user_in_member_list(
- room_id, handler.store
+ room_id, self._main_store
):
continue
diff --git a/synapse/rest/media/v1/upload_resource.py b/synapse/rest/media/v1/upload_resource.py
index 8162094cf6..fde28d08cb 100644
--- a/synapse/rest/media/v1/upload_resource.py
+++ b/synapse/rest/media/v1/upload_resource.py
@@ -49,10 +49,14 @@ class UploadResource(DirectServeJsonResource):
async def _async_render_POST(self, request: SynapseRequest) -> None:
requester = await self.auth.get_user_by_req(request)
- content_length = request.getHeader("Content-Length")
- if content_length is None:
+ raw_content_length = request.getHeader("Content-Length")
+ if raw_content_length is None:
raise SynapseError(msg="Request must specify a Content-Length", code=400)
- if int(content_length) > self.max_upload_size:
+ try:
+ content_length = int(raw_content_length)
+ except ValueError:
+ raise SynapseError(msg="Content-Length value is invalid", code=400)
+ if content_length > self.max_upload_size:
raise SynapseError(
msg="Upload request body is too large",
code=413,
@@ -66,7 +70,8 @@ class UploadResource(DirectServeJsonResource):
upload_name: Optional[str] = upload_name_bytes.decode("utf8")
except UnicodeDecodeError:
raise SynapseError(
- msg="Invalid UTF-8 filename parameter: %r" % (upload_name), code=400
+ msg="Invalid UTF-8 filename parameter: %r" % (upload_name_bytes,),
+ code=400,
)
# If the name is falsey (e.g. an empty byte string) ensure it is None.
diff --git a/tests/appservice/test_appservice.py b/tests/appservice/test_appservice.py
index 07d8105f41..9bd6275e92 100644
--- a/tests/appservice/test_appservice.py
+++ b/tests/appservice/test_appservice.py
@@ -40,13 +40,19 @@ class ApplicationServiceTestCase(unittest.TestCase):
)
self.store = Mock()
+ self.store.get_aliases_for_room = simple_async_mock([])
+ self.store.get_users_in_room = simple_async_mock([])
@defer.inlineCallbacks
def test_regex_user_id_prefix_match(self):
self.service.namespaces[ApplicationService.NS_USERS].append(_regex("@irc_.*"))
self.event.sender = "@irc_foobar:matrix.org"
self.assertTrue(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
@@ -54,7 +60,11 @@ class ApplicationServiceTestCase(unittest.TestCase):
self.service.namespaces[ApplicationService.NS_USERS].append(_regex("@irc_.*"))
self.event.sender = "@someone_else:matrix.org"
self.assertFalse(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
@@ -64,7 +74,11 @@ class ApplicationServiceTestCase(unittest.TestCase):
self.event.type = "m.room.member"
self.event.state_key = "@irc_foobar:matrix.org"
self.assertTrue(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
@@ -74,7 +88,11 @@ class ApplicationServiceTestCase(unittest.TestCase):
)
self.event.room_id = "!some_prefixs0m3th1nGsome_suffix:matrix.org"
self.assertTrue(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
@@ -84,7 +102,11 @@ class ApplicationServiceTestCase(unittest.TestCase):
)
self.event.room_id = "!XqBunHwQIXUiqCaoxq:matrix.org"
self.assertFalse(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
@@ -183,7 +205,11 @@ class ApplicationServiceTestCase(unittest.TestCase):
self.event.content = {"membership": "invite"}
self.event.state_key = self.service.sender
self.assertTrue(
- (yield defer.ensureDeferred(self.service.is_interested(self.event)))
+ (
+ yield defer.ensureDeferred(
+ self.service.is_interested(self.event, self.store)
+ )
+ )
)
@defer.inlineCallbacks
diff --git a/tests/rest/client/test_sync.py b/tests/rest/client/test_sync.py
index c427686376..cd4af2b1f3 100644
--- a/tests/rest/client/test_sync.py
+++ b/tests/rest/client/test_sync.py
@@ -23,7 +23,7 @@ from synapse.api.constants import (
ReadReceiptEventFields,
RelationTypes,
)
-from synapse.rest.client import knock, login, read_marker, receipts, room, sync
+from synapse.rest.client import devices, knock, login, read_marker, receipts, room, sync
from tests import unittest
from tests.federation.transport.test_knocking import (
@@ -710,3 +710,58 @@ class SyncCacheTestCase(unittest.HomeserverTestCase):
channel.await_result(timeout_ms=9900)
channel.await_result(timeout_ms=200)
self.assertEqual(channel.code, 200, channel.json_body)
+
+
+class DeviceListSyncTestCase(unittest.HomeserverTestCase):
+ servlets = [
+ synapse.rest.admin.register_servlets,
+ login.register_servlets,
+ sync.register_servlets,
+ devices.register_servlets,
+ ]
+
+ def test_user_with_no_rooms_receives_self_device_list_updates(self):
+ """Tests that a user with no rooms still receives their own device list updates"""
+ device_id = "TESTDEVICE"
+
+ # Register a user and login, creating a device
+ self.user_id = self.register_user("kermit", "monkey")
+ self.tok = self.login("kermit", "monkey", device_id=device_id)
+
+ # Request an initial sync
+ channel = self.make_request("GET", "/sync", access_token=self.tok)
+ self.assertEqual(channel.code, 200, channel.json_body)
+ next_batch = channel.json_body["next_batch"]
+
+ # Now, make an incremental sync request.
+ # It won't return until something has happened
+ incremental_sync_channel = self.make_request(
+ "GET",
+ f"/sync?since={next_batch}&timeout=30000",
+ access_token=self.tok,
+ await_result=False,
+ )
+
+ # Change our device's display name
+ channel = self.make_request(
+ "PUT",
+ f"devices/{device_id}",
+ {
+ "display_name": "freeze ray",
+ },
+ access_token=self.tok,
+ )
+ self.assertEqual(channel.code, 200, channel.json_body)
+
+ # The sync should now have returned
+ incremental_sync_channel.await_result(timeout_ms=20000)
+ self.assertEqual(incremental_sync_channel.code, 200, channel.json_body)
+
+ # We should have received notification that the (user's) device has changed
+ device_list_changes = incremental_sync_channel.json_body.get(
+ "device_lists", {}
+ ).get("changed", [])
+
+ self.assertIn(
+ self.user_id, device_list_changes, incremental_sync_channel.json_body
+ )
|