diff --git a/synapse/config/experimental.py b/synapse/config/experimental.py
index 75fe6d7b24..5fe5b951dd 100644
--- a/synapse/config/experimental.py
+++ b/synapse/config/experimental.py
@@ -443,3 +443,6 @@ class ExperimentalConfig(Config):
self.msc3916_authenticated_media_enabled = experimental.get(
"msc3916_authenticated_media_enabled", False
)
+
+ # MSC4151: Report room API (Client-Server API)
+ self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False)
diff --git a/synapse/rest/__init__.py b/synapse/rest/__init__.py
index 534dc0e276..0024ccf708 100644
--- a/synapse/rest/__init__.py
+++ b/synapse/rest/__init__.py
@@ -53,7 +53,7 @@ from synapse.rest.client import (
register,
relations,
rendezvous,
- report_event,
+ reporting,
room,
room_keys,
room_upgrade_rest_servlet,
@@ -128,7 +128,7 @@ class ClientRestResource(JsonResource):
tags.register_servlets(hs, client_resource)
account_data.register_servlets(hs, client_resource)
if is_main_process:
- report_event.register_servlets(hs, client_resource)
+ reporting.register_servlets(hs, client_resource)
openid.register_servlets(hs, client_resource)
notifications.register_servlets(hs, client_resource)
devices.register_servlets(hs, client_resource)
diff --git a/synapse/rest/client/report_event.py b/synapse/rest/client/reporting.py
index 447281931e..a95b83b14d 100644
--- a/synapse/rest/client/report_event.py
+++ b/synapse/rest/client/reporting.py
@@ -23,17 +23,28 @@ import logging
from http import HTTPStatus
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.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.types import JsonDict
+from synapse.types.rest import RequestBodyModel
from ._base import client_patterns
if TYPE_CHECKING:
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__)
@@ -95,5 +106,49 @@ class ReportEventRestServlet(RestServlet):
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:
ReportEventRestServlet(hs).register(http_server)
+
+ if hs.config.experimental.msc4151_enabled:
+ ReportRoomRestServlet(hs).register(http_server)
diff --git a/synapse/rest/client/versions.py b/synapse/rest/client/versions.py
index 56de6906d0..f428158139 100644
--- a/synapse/rest/client/versions.py
+++ b/synapse/rest/client/versions.py
@@ -149,6 +149,8 @@ class VersionsRestServlet(RestServlet):
is not None
)
),
+ # MSC4151: Report room API (Client-Server API)
+ "org.matrix.msc4151": self.config.experimental.msc4151_enabled,
},
},
)
diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py
index 616c941687..b8a71c803e 100644
--- a/synapse/storage/databases/main/room.py
+++ b/synapse/storage/databases/main/room.py
@@ -2207,6 +2207,7 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore):
super().__init__(database, db_conn, hs)
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()
@@ -2416,6 +2417,37 @@ class RoomStore(RoomBackgroundUpdateStore, RoomWorkerStore):
)
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:
"""Marks the room as blocked.
diff --git a/synapse/storage/schema/main/delta/85/06_add_room_reports.sql b/synapse/storage/schema/main/delta/85/06_add_room_reports.sql
new file mode 100644
index 0000000000..f7b45276cf
--- /dev/null
+++ b/synapse/storage/schema/main/delta/85/06_add_room_reports.sql
@@ -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
+);
|