Merge branch 'develop' into madlittlemods/msc3575-sliding-sync-filter-dms

This commit is contained in:
Eric Eastwood 2024-06-12 16:38:18 -05:00
commit 9896478297
17 changed files with 274 additions and 30 deletions

View file

@ -14,7 +14,7 @@ jobs:
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action # There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess: # (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
- name: 📥 Download artifact - name: 📥 Download artifact
uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4 uses: dawidd6/action-download-artifact@deb3bb83256a78589fef6a7b942e5f2573ad7c13 # v5
with: with:
workflow: docs-pr.yaml workflow: docs-pr.yaml
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}

View file

@ -1,3 +1,13 @@
# Synapse 1.109.0rc2 (2024-06-11)
### Bugfixes
- Fix bug where one-time-keys were not always included in `/sync` response when using workers. Introduced in v1.109.0rc1. ([\#17275](https://github.com/element-hq/synapse/issues/17275))
- Fix bug where `/sync` could get stuck due to edge case in device lists handling. Introduced in v1.109.0rc1. ([\#17292](https://github.com/element-hq/synapse/issues/17292))
# Synapse 1.109.0rc1 (2024-06-04) # Synapse 1.109.0rc1 (2024-06-04)
### Features ### Features

4
Cargo.lock generated
View file

@ -444,9 +444,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",

View file

@ -0,0 +1 @@
Add support for the unstable [MSC4151](https://github.com/matrix-org/matrix-spec-proposals/pull/4151) report room API.

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
matrix-synapse-py3 (1.109.0~rc2) stable; urgency=medium
* New synapse release 1.109.0rc2.
-- Synapse Packaging team <packages@matrix.org> Tue, 11 Jun 2024 13:20:17 +0000
matrix-synapse-py3 (1.109.0~rc1) stable; urgency=medium matrix-synapse-py3 (1.109.0~rc1) stable; urgency=medium
* New Synapse release 1.109.0rc1. * New Synapse release 1.109.0rc1.

18
poetry.lock generated
View file

@ -912,13 +912,13 @@ trio = ["async_generator", "trio"]
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.3" version = "3.1.4"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
{file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
] ]
[package.dependencies] [package.dependencies]
@ -2808,13 +2808,13 @@ files = [
[[package]] [[package]]
name = "types-jsonschema" name = "types-jsonschema"
version = "4.21.0.20240311" version = "4.22.0.20240610"
description = "Typing stubs for jsonschema" description = "Typing stubs for jsonschema"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-jsonschema-4.21.0.20240311.tar.gz", hash = "sha256:f7165ce70abd91df490c73b089873afd2899c5e56430ee495b64f851ad01f287"}, {file = "types-jsonschema-4.22.0.20240610.tar.gz", hash = "sha256:f82ab9fe756e3a2642ea9712c46b403ce61eb380b939b696cff3252af42f65b0"},
{file = "types_jsonschema-4.21.0.20240311-py3-none-any.whl", hash = "sha256:e872f5661513824edf9698f73a66c9c114713d93eab58699bd0532e7e6db5750"}, {file = "types_jsonschema-4.22.0.20240610-py3-none-any.whl", hash = "sha256:89996b9bd1928f820a0e252b2844be21cd2e55d062b6fa1048d88453006ad89e"},
] ]
[package.dependencies] [package.dependencies]
@ -2844,13 +2844,13 @@ files = [
[[package]] [[package]]
name = "types-pillow" name = "types-pillow"
version = "10.2.0.20240423" version = "10.2.0.20240520"
description = "Typing stubs for Pillow" description = "Typing stubs for Pillow"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "types-Pillow-10.2.0.20240423.tar.gz", hash = "sha256:696e68b9b6a58548fc307a8669830469237c5b11809ddf978ac77fafa79251cd"}, {file = "types-Pillow-10.2.0.20240520.tar.gz", hash = "sha256:130b979195465fa1e1676d8e81c9c7c30319e8e95b12fae945e8f0d525213107"},
{file = "types_Pillow-10.2.0.20240423-py3-none-any.whl", hash = "sha256:bd12923093b96c91d523efcdb66967a307f1a843bcfaf2d5a529146c10a9ced3"}, {file = "types_Pillow-10.2.0.20240520-py3-none-any.whl", hash = "sha256:33c36494b380e2a269bb742181bea5d9b00820367822dbd3760f07210a1da23d"},
] ]
[[package]] [[package]]

View file

@ -96,7 +96,7 @@ module-name = "synapse.synapse_rust"
[tool.poetry] [tool.poetry]
name = "matrix-synapse" name = "matrix-synapse"
version = "1.109.0rc1" version = "1.109.0rc2"
description = "Homeserver for the Matrix decentralised comms protocol" description = "Homeserver for the Matrix decentralised comms protocol"
authors = ["Matrix.org Team and Contributors <packages@matrix.org>"] authors = ["Matrix.org Team and Contributors <packages@matrix.org>"]
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"

View file

@ -443,3 +443,6 @@ class ExperimentalConfig(Config):
self.msc3916_authenticated_media_enabled = experimental.get( self.msc3916_authenticated_media_enabled = experimental.get(
"msc3916_authenticated_media_enabled", False "msc3916_authenticated_media_enabled", False
) )
# MSC4151: Report room API (Client-Server API)
self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False)

View file

@ -53,7 +53,7 @@ from synapse.rest.client import (
register, register,
relations, relations,
rendezvous, rendezvous,
report_event, reporting,
room, room,
room_keys, room_keys,
room_upgrade_rest_servlet, room_upgrade_rest_servlet,
@ -128,7 +128,7 @@ class ClientRestResource(JsonResource):
tags.register_servlets(hs, client_resource) tags.register_servlets(hs, client_resource)
account_data.register_servlets(hs, client_resource) account_data.register_servlets(hs, client_resource)
if is_main_process: if is_main_process:
report_event.register_servlets(hs, client_resource) reporting.register_servlets(hs, client_resource)
openid.register_servlets(hs, client_resource) openid.register_servlets(hs, client_resource)
notifications.register_servlets(hs, client_resource) notifications.register_servlets(hs, client_resource)
devices.register_servlets(hs, client_resource) devices.register_servlets(hs, client_resource)

View file

@ -23,17 +23,28 @@ import logging
from http import HTTPStatus from http import HTTPStatus
from typing import TYPE_CHECKING, Tuple from typing import TYPE_CHECKING, Tuple
from synapse._pydantic_compat import HAS_PYDANTIC_V2
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
from synapse.http.server import HttpServer from synapse.http.server import HttpServer
from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.servlet import (
RestServlet,
parse_and_validate_json_object_from_request,
parse_json_object_from_request,
)
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.types import JsonDict from synapse.types import JsonDict
from synapse.types.rest import RequestBodyModel
from ._base import client_patterns from ._base import client_patterns
if TYPE_CHECKING: if TYPE_CHECKING:
from synapse.server import HomeServer from synapse.server import HomeServer
if TYPE_CHECKING or HAS_PYDANTIC_V2:
from pydantic.v1 import StrictStr
else:
from pydantic import StrictStr
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,5 +106,49 @@ class ReportEventRestServlet(RestServlet):
return 200, {} return 200, {}
class ReportRoomRestServlet(RestServlet):
# https://github.com/matrix-org/matrix-spec-proposals/pull/4151
PATTERNS = client_patterns(
"/org.matrix.msc4151/rooms/(?P<room_id>[^/]*)/report$",
releases=[],
v1=False,
unstable=True,
)
def __init__(self, hs: "HomeServer"):
super().__init__()
self.hs = hs
self.auth = hs.get_auth()
self.clock = hs.get_clock()
self.store = hs.get_datastores().main
class PostBody(RequestBodyModel):
reason: StrictStr
async def on_POST(
self, request: SynapseRequest, room_id: str
) -> Tuple[int, JsonDict]:
requester = await self.auth.get_user_by_req(request)
user_id = requester.user.to_string()
body = parse_and_validate_json_object_from_request(request, self.PostBody)
room = await self.store.get_room(room_id)
if room is None:
raise NotFoundError("Room does not exist")
await self.store.add_room_report(
room_id=room_id,
user_id=user_id,
reason=body.reason,
received_ts=self.clock.time_msec(),
)
return 200, {}
def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
ReportEventRestServlet(hs).register(http_server) ReportEventRestServlet(hs).register(http_server)
if hs.config.experimental.msc4151_enabled:
ReportRoomRestServlet(hs).register(http_server)

View file

@ -149,6 +149,8 @@ class VersionsRestServlet(RestServlet):
is not None is not None
) )
), ),
# MSC4151: Report room API (Client-Server API)
"org.matrix.msc4151": self.config.experimental.msc4151_enabled,
}, },
}, },
) )

View file

@ -108,6 +108,11 @@ class DeviceWorkerStore(RoomMemberWorkerStore, EndToEndKeyWorkerStore):
("device_lists_outbound_pokes", "instance_name", "stream_id"), ("device_lists_outbound_pokes", "instance_name", "stream_id"),
("device_lists_changes_in_room", "instance_name", "stream_id"), ("device_lists_changes_in_room", "instance_name", "stream_id"),
("device_lists_remote_pending", "instance_name", "stream_id"), ("device_lists_remote_pending", "instance_name", "stream_id"),
(
"device_lists_changes_converted_stream_position",
"instance_name",
"stream_id",
),
], ],
sequence_name="device_lists_sequence", sequence_name="device_lists_sequence",
writers=["master"], writers=["master"],
@ -2394,15 +2399,16 @@ class DeviceStore(DeviceWorkerStore, DeviceBackgroundUpdateStore):
`FALSE` have not been converted. `FALSE` have not been converted.
""" """
return cast( # There should be only one row in this table, though we want to
Tuple[int, str], # future-proof ourselves for when we have multiple rows (one for each
await self.db_pool.simple_select_one( # instance). So to handle that case we take the minimum of all rows.
table="device_lists_changes_converted_stream_position", rows = await self.db_pool.simple_select_list(
keyvalues={}, table="device_lists_changes_converted_stream_position",
retcols=["stream_id", "room_id"], keyvalues={},
desc="get_device_change_last_converted_pos", retcols=["stream_id", "room_id"],
), desc="get_device_change_last_converted_pos",
) )
return cast(Tuple[int, str], min(rows))
async def set_device_change_last_converted_pos( async def set_device_change_last_converted_pos(
self, self,
@ -2417,6 +2423,10 @@ class DeviceStore(DeviceWorkerStore, DeviceBackgroundUpdateStore):
await self.db_pool.simple_update_one( await self.db_pool.simple_update_one(
table="device_lists_changes_converted_stream_position", table="device_lists_changes_converted_stream_position",
keyvalues={}, keyvalues={},
updatevalues={"stream_id": stream_id, "room_id": room_id}, updatevalues={
"stream_id": stream_id,
"instance_name": self._instance_name,
"room_id": room_id,
},
desc="set_device_change_last_converted_pos", desc="set_device_change_last_converted_pos",
) )

View file

@ -2207,6 +2207,7 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore):
super().__init__(database, db_conn, hs) super().__init__(database, db_conn, hs)
self._event_reports_id_gen = IdGenerator(db_conn, "event_reports", "id") self._event_reports_id_gen = IdGenerator(db_conn, "event_reports", "id")
self._room_reports_id_gen = IdGenerator(db_conn, "room_reports", "id")
self._instance_name = hs.get_instance_name() self._instance_name = hs.get_instance_name()
@ -2416,6 +2417,37 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore):
) )
return next_id return next_id
async def add_room_report(
self,
room_id: str,
user_id: str,
reason: str,
received_ts: int,
) -> int:
"""Add a room report
Args:
room_id: The room ID being reported.
user_id: User who reports the room.
reason: Description that the user specifies.
received_ts: Time when the user submitted the report (milliseconds).
Returns:
Id of the room report.
"""
next_id = self._room_reports_id_gen.get_next()
await self.db_pool.simple_insert(
table="room_reports",
values={
"id": next_id,
"received_ts": received_ts,
"room_id": room_id,
"user_id": user_id,
"reason": reason,
},
desc="add_room_report",
)
return next_id
async def block_room(self, room_id: str, user_id: str) -> None: async def block_room(self, room_id: str, user_id: str) -> None:
"""Marks the room as blocked. """Marks the room as blocked.

View file

@ -0,0 +1,16 @@
--
-- This file is licensed under the Affero General Public License (AGPL) version 3.
--
-- Copyright (C) 2024 New Vector, Ltd
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- See the GNU Affero General Public License for more details:
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
-- Add `instance_name` columns to stream tables to allow them to be used with
-- `MultiWriterIdGenerator`
ALTER TABLE device_lists_changes_converted_stream_position ADD COLUMN instance_name TEXT;

View file

@ -0,0 +1,20 @@
--
-- This file is licensed under the Affero General Public License (AGPL) version 3.
--
-- Copyright (C) 2024 New Vector, Ltd
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
--
-- See the GNU Affero General Public License for more details:
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
CREATE TABLE room_reports (
id BIGINT NOT NULL PRIMARY KEY,
received_ts BIGINT NOT NULL,
room_id TEXT NOT NULL,
user_id TEXT NOT NULL,
reason TEXT NOT NULL
);

View file

@ -24,7 +24,7 @@ from twisted.test.proto_helpers import MemoryReactor
import synapse.rest.admin import synapse.rest.admin
from synapse.api.errors import Codes from synapse.api.errors import Codes
from synapse.rest.client import login, report_event, room from synapse.rest.client import login, reporting, room
from synapse.server import HomeServer from synapse.server import HomeServer
from synapse.types import JsonDict from synapse.types import JsonDict
from synapse.util import Clock from synapse.util import Clock
@ -37,7 +37,7 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
synapse.rest.admin.register_servlets, synapse.rest.admin.register_servlets,
login.register_servlets, login.register_servlets,
room.register_servlets, room.register_servlets,
report_event.register_servlets, reporting.register_servlets,
] ]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@ -453,7 +453,7 @@ class EventReportDetailTestCase(unittest.HomeserverTestCase):
synapse.rest.admin.register_servlets, synapse.rest.admin.register_servlets,
login.register_servlets, login.register_servlets,
room.register_servlets, room.register_servlets,
report_event.register_servlets, reporting.register_servlets,
] ]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:

View file

@ -22,7 +22,7 @@
from twisted.test.proto_helpers import MemoryReactor from twisted.test.proto_helpers import MemoryReactor
import synapse.rest.admin import synapse.rest.admin
from synapse.rest.client import login, report_event, room from synapse.rest.client import login, reporting, room
from synapse.server import HomeServer from synapse.server import HomeServer
from synapse.types import JsonDict from synapse.types import JsonDict
from synapse.util import Clock from synapse.util import Clock
@ -35,7 +35,7 @@ class ReportEventTestCase(unittest.HomeserverTestCase):
synapse.rest.admin.register_servlets, synapse.rest.admin.register_servlets,
login.register_servlets, login.register_servlets,
room.register_servlets, room.register_servlets,
report_event.register_servlets, reporting.register_servlets,
] ]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
@ -139,3 +139,92 @@ class ReportEventTestCase(unittest.HomeserverTestCase):
"POST", self.report_path, data, access_token=self.other_user_tok "POST", self.report_path, data, access_token=self.other_user_tok
) )
self.assertEqual(response_status, channel.code, msg=channel.result["body"]) self.assertEqual(response_status, channel.code, msg=channel.result["body"])
class ReportRoomTestCase(unittest.HomeserverTestCase):
servlets = [
synapse.rest.admin.register_servlets,
login.register_servlets,
room.register_servlets,
reporting.register_servlets,
]
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
self.other_user = self.register_user("user", "pass")
self.other_user_tok = self.login("user", "pass")
self.room_id = self.helper.create_room_as(
self.other_user, tok=self.other_user_tok, is_public=True
)
self.report_path = (
f"/_matrix/client/unstable/org.matrix.msc4151/rooms/{self.room_id}/report"
)
@unittest.override_config(
{
"experimental_features": {"msc4151_enabled": True},
}
)
def test_reason_str(self) -> None:
data = {"reason": "this makes me sad"}
self._assert_status(200, data)
@unittest.override_config(
{
"experimental_features": {"msc4151_enabled": True},
}
)
def test_no_reason(self) -> None:
data = {"not_reason": "for typechecking"}
self._assert_status(400, data)
@unittest.override_config(
{
"experimental_features": {"msc4151_enabled": True},
}
)
def test_reason_nonstring(self) -> None:
data = {"reason": 42}
self._assert_status(400, data)
@unittest.override_config(
{
"experimental_features": {"msc4151_enabled": True},
}
)
def test_reason_null(self) -> None:
data = {"reason": None}
self._assert_status(400, data)
@unittest.override_config(
{
"experimental_features": {"msc4151_enabled": True},
}
)
def test_cannot_report_nonexistent_room(self) -> None:
"""
Tests that we don't accept event reports for rooms which do not exist.
"""
channel = self.make_request(
"POST",
"/_matrix/client/unstable/org.matrix.msc4151/rooms/!bloop:example.org/report",
{"reason": "i am very sad"},
access_token=self.other_user_tok,
shorthand=False,
)
self.assertEqual(404, channel.code, msg=channel.result["body"])
self.assertEqual(
"Room does not exist",
channel.json_body["error"],
msg=channel.result["body"],
)
def _assert_status(self, response_status: int, data: JsonDict) -> None:
channel = self.make_request(
"POST",
self.report_path,
data,
access_token=self.other_user_tok,
shorthand=False,
)
self.assertEqual(response_status, channel.code, msg=channel.result["body"])