From 4d6174a9477a34107570ae169915b4cedd2ac86d Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Wed, 14 Oct 2020 10:00:29 +0200 Subject: [PATCH 1/9] Split admin API for reported events in detail und list view --- docs/admin_api/event_reports.rst | 130 +++++++++++++------ synapse/rest/admin/__init__.py | 6 +- synapse/rest/admin/event_reports.py | 44 ++++++- synapse/storage/databases/main/room.py | 54 +++++++- tests/rest/admin/test_event_reports.py | 173 +++++++++++++++++++++++++ 5 files changed, 362 insertions(+), 45 deletions(-) diff --git a/docs/admin_api/event_reports.rst b/docs/admin_api/event_reports.rst index 461be012300e..8d80fe8ca604 100644 --- a/docs/admin_api/event_reports.rst +++ b/docs/admin_api/event_reports.rst @@ -22,39 +22,6 @@ It returns a JSON body like the following: "score": -100 }, "event_id": "$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY", - "event_json": { - "auth_events": [ - "$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M", - "$oggsNXxzPFRE3y53SUNd7nsj69-QzKv03a1RucHu-ws" - ], - "content": { - "body": "matrix.org: This Week in Matrix", - "format": "org.matrix.custom.html", - "formatted_body": "matrix.org:
This Week in Matrix", - "msgtype": "m.notice" - }, - "depth": 546, - "hashes": { - "sha256": "xK1//xnmvHJIOvbgXlkI8eEqdvoMmihVDJ9J4SNlsAw" - }, - "origin": "matrix.org", - "origin_server_ts": 1592291711430, - "prev_events": [ - "$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M" - ], - "prev_state": [], - "room_id": "!ERAgBpSOcCCuTJqQPk:matrix.org", - "sender": "@foobar:matrix.org", - "signatures": { - "matrix.org": { - "ed25519:a_JaEG": "cs+OUKW/iHx5pEidbWxh0UiNNHwe46Ai9LwNz+Ah16aWDNszVIe2gaAcVZfvNsBhakQTew51tlKmL2kspXk/Dg" - } - }, - "type": "m.room.message", - "unsigned": { - "age_ts": 1592291711430, - } - }, "id": 2, "reason": "foo", "received_ts": 1570897107409, @@ -69,10 +36,6 @@ It returns a JSON body like the following: "score": -100 }, "event_id": "$3IcdZsDaN_En-S1DF4EMCy3v4gNRKeOJs8W5qTOKj4I", - "event_json": { - // hidden items - // see above - }, "id": 3, "reason": "bar", "received_ts": 1598889612059, @@ -123,7 +86,98 @@ The following fields are returned in the JSON response body: - ``sender``: string - This is the ID of the user who sent the original message/event that was reported. - ``room_alias``: string - The alias of the room. ``null`` if the room does not have a canonical alias set. -- ``event_json``: object - Details of the original event that was reported. - ``next_token``: integer - Indication for pagination. See above. - ``total``: integer - Total number of event reports related to the query (``user_id`` and ``room_id``). +Show details of a specific event +================================ + +This API returns information about a specific reported event. + +The api is:: + + GET /_synapse/admin/v1/event_reports/ + +To use it, you will need to authenticate by providing an ``access_token`` for a +server admin: see `README.rst `_. + +It returns a JSON body like the following: + +.. code:: jsonc + + { + "content": { + "reason": "foo", + "score": -100 + }, + "event_id": "$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY", + "event_json": { + "auth_events": [ + "$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M", + "$oggsNXxzPFRE3y53SUNd7nsj69-QzKv03a1RucHu-ws" + ], + "content": { + "body": "matrix.org: This Week in Matrix", + "format": "org.matrix.custom.html", + "formatted_body": "matrix.org:
This Week in Matrix", + "msgtype": "m.notice" + }, + "depth": 546, + "hashes": { + "sha256": "xK1//xnmvHJIOvbgXlkI8eEqdvoMmihVDJ9J4SNlsAw" + }, + "origin": "matrix.org", + "origin_server_ts": 1592291711430, + "prev_events": [ + "$YK4arsKKcc0LRoe700pS8DSjOvUT4NDv0HfInlMFw2M" + ], + "prev_state": [], + "room_id": "!ERAgBpSOcCCuTJqQPk:matrix.org", + "sender": "@foobar:matrix.org", + "signatures": { + "matrix.org": { + "ed25519:a_JaEG": "cs+OUKW/iHx5pEidbWxh0UiNNHwe46Ai9LwNz+Ah16aWDNszVIe2gaAcVZfvNsBhakQTew51tlKmL2kspXk/Dg" + } + }, + "type": "m.room.message", + "unsigned": { + "age_ts": 1592291711430, + } + }, + "id": , + "reason": "foo", + "received_ts": 1570897107409, + "room_alias": "#alias1:matrix.org", + "room_id": "!ERAgBpSOcCCuTJqQPk:matrix.org", + "sender": "@foobar:matrix.org", + "user_id": "@foo:matrix.org" + } + +To paginate, check for ``next_token`` and if present, call the endpoint again +with ``from`` set to the value of ``next_token``. This will return a new page. + +If the endpoint does not return a ``next_token`` then there are no more +reports to paginate through. + +**URL parameters:** + +- ``report_id``: string - The ID of the reported event. + +**Response** + +The following fields are returned in the JSON response body: + +- ``id``: integer - ID of event report. +- ``received_ts``: integer - The timestamp (in milliseconds since the unix epoch) when this report was sent. +- ``room_id``: string - The ID of the room in which the event being reported is located. +- ``event_id``: string - The ID of the reported event. +- ``user_id``: string - This is the user who reported the event and wrote the reason. +- ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. +- ``content``: object - Content of reported event. + + - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. + - ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". + +- ``sender``: string - This is the ID of the user who sent the original message/event that was reported. +- ``room_alias``: string - The alias of the room. ``null`` if the room does not have a canonical alias set. +- ``event_json``: object - Details of the original event that was reported. diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py index 789431ef25b2..df14bdf26ebc 100644 --- a/synapse/rest/admin/__init__.py +++ b/synapse/rest/admin/__init__.py @@ -31,7 +31,10 @@ DeviceRestServlet, DevicesRestServlet, ) -from synapse.rest.admin.event_reports import EventReportsRestServlet +from synapse.rest.admin.event_reports import ( + EventReportDetailRestServlet, + EventReportsRestServlet, +) from synapse.rest.admin.groups import DeleteGroupAdminRestServlet from synapse.rest.admin.media import ListMediaInRoom, register_servlets_for_media_repo from synapse.rest.admin.purge_room_servlet import PurgeRoomServlet @@ -222,6 +225,7 @@ def register_servlets(hs, http_server): DevicesRestServlet(hs).register(http_server) DeleteDevicesRestServlet(hs).register(http_server) EventReportsRestServlet(hs).register(http_server) + EventReportDetailRestServlet(hs).register(http_server) def register_servlets_for_client_rest_resource(hs, http_server): diff --git a/synapse/rest/admin/event_reports.py b/synapse/rest/admin/event_reports.py index 5b8d0594cddc..d017a3ababe5 100644 --- a/synapse/rest/admin/event_reports.py +++ b/synapse/rest/admin/event_reports.py @@ -15,7 +15,7 @@ import logging -from synapse.api.errors import Codes, SynapseError +from synapse.api.errors import Codes, NotFoundError, SynapseError from synapse.http.servlet import RestServlet, parse_integer, parse_string from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin @@ -86,3 +86,45 @@ async def on_GET(self, request): ret["next_token"] = start + len(event_reports) return 200, ret + + +class EventReportDetailRestServlet(RestServlet): + """ + Get a specific reported event that are known to the homeserver. Results are returned + in a dictionary containing report information. + The requester must have administrator access in Synapse. + + GET /_synapse/admin/v1/event_reports/ + returns: + 200 OK with details report if success otherwise an error. + + Args: + The parameter `report_id` is the ID of reported event in database. + Returns: + json list of information from event report + """ + + PATTERNS = admin_patterns("/event_reports/(?P[^/]*)$") + + def __init__(self, hs): + self.hs = hs + self.auth = hs.get_auth() + self.store = hs.get_datastore() + + async def on_GET(self, request, report_id): + await assert_requester_is_admin(self.auth, request) + + message = "The report_id parameter must be a positive integer." + try: + report_id = int(report_id) + except Exception: + raise SynapseError(400, message, errcode=Codes.INVALID_PARAM) + + if report_id < 0: + raise SynapseError(400, message, errcode=Codes.INVALID_PARAM) + + ret = await self.store.get_event_report(report_id) + if not ret: + raise NotFoundError("Event report not found") + + return 200, ret diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index e83d961c20b4..36dda061a870 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1411,6 +1411,54 @@ async def add_event_report( desc="add_event_report", ) + async def get_event_report(self, report_id: int) -> Dict[str, Any]: + """Retrieve a an event report + + Args: + report_id: ID of reported event in database + Returns: + event_report: json list of information from event report + """ + + def _get_event_report_txn(txn, report_id): + + sql = """ + SELECT + er.id, + er.received_ts, + er.room_id, + er.event_id, + er.user_id, + er.reason, + er.content, + events.sender, + room_aliases.room_alias, + event_json.json AS event_json + FROM event_reports AS er + LEFT JOIN room_aliases + ON room_aliases.room_id = er.room_id + JOIN events + ON events.event_id = er.event_id + JOIN event_json + ON event_json.event_id = er.event_id + WHERE er.id = ? + """ + + txn.execute(sql, [report_id]) + event_report = self.db_pool.cursor_to_dict(txn) + + try: + event_report["content"] = db_to_json(event_report["content"]) + event_report["event_json"] = db_to_json(event_report["event_json"]) + except Exception: + pass + + return event_report + + return await self.db_pool.runInteraction( + "get_event_report", _get_event_report_txn, report_id + ) + async def get_event_reports_paginate( self, start: int, @@ -1471,15 +1519,12 @@ def _get_event_reports_paginate_txn(txn): er.reason, er.content, events.sender, - room_aliases.room_alias, - event_json.json AS event_json + room_aliases.room_alias FROM event_reports AS er LEFT JOIN room_aliases ON room_aliases.room_id = er.room_id JOIN events ON events.event_id = er.event_id - JOIN event_json - ON event_json.event_id = er.event_id {where_clause} ORDER BY er.received_ts {order} LIMIT ? @@ -1496,7 +1541,6 @@ def _get_event_reports_paginate_txn(txn): for row in event_reports: try: row["content"] = db_to_json(row["content"]) - row["event_json"] = db_to_json(row["event_json"]) except Exception: continue diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py index bf79086f7813..d45769ccbe6a 100644 --- a/tests/rest/admin/test_event_reports.py +++ b/tests/rest/admin/test_event_reports.py @@ -70,6 +70,16 @@ def prepare(self, reactor, clock, hs): self.url = "/_synapse/admin/v1/event_reports" + def test_no_auth(self): + """ + Try to get event report without authentication. + """ + request, channel = self.make_request("GET", self.url, b"{}") + self.render(request) + + self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"]) + def test_requester_is_no_admin(self): """ If the user is not a server admin, an error 403 is returned. @@ -359,6 +369,169 @@ def _create_event_and_report(self, room_id, user_tok): self.render(request) self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) + def _check_fields(self, content): + """Checks that all attributes are present in a event report + """ + for c in content: + self.assertIn("id", c) + self.assertIn("received_ts", c) + self.assertIn("room_id", c) + self.assertIn("event_id", c) + self.assertIn("user_id", c) + self.assertIn("reason", c) + self.assertIn("content", c) + self.assertIn("sender", c) + self.assertIn("room_alias", c) + self.assertIn("score", c["content"]) + self.assertIn("reason", c["content"]) + + +class EventReportDetailTestCase(unittest.HomeserverTestCase): + servlets = [ + synapse.rest.admin.register_servlets, + login.register_servlets, + room.register_servlets, + report_event.register_servlets, + ] + + def prepare(self, reactor, clock, hs): + self.store = hs.get_datastore() + + self.admin_user = self.register_user("admin", "pass", admin=True) + self.admin_user_tok = self.login("admin", "pass") + + self.other_user = self.register_user("user", "pass") + self.other_user_tok = self.login("user", "pass") + + self.room_id1 = self.helper.create_room_as( + self.other_user, tok=self.other_user_tok, is_public=True + ) + self.helper.join(self.room_id1, user=self.admin_user, tok=self.admin_user_tok) + + self._create_event_and_report( + room_id=self.room_id1, user_tok=self.other_user_tok, + ) + + # first created event report gets `id`=2 + self.url = "/_synapse/admin/v1/event_reports/2" + + def test_no_auth(self): + """ + Try to get event report without authentication. + """ + request, channel = self.make_request("GET", self.url, b"{}") + self.render(request) + + self.assertEqual(401, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.MISSING_TOKEN, channel.json_body["errcode"]) + + def test_requester_is_no_admin(self): + """ + If the user is not a server admin, an error 403 is returned. + """ + + request, channel = self.make_request( + "GET", self.url, access_token=self.other_user_tok, + ) + self.render(request) + + self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.FORBIDDEN, channel.json_body["errcode"]) + + def test_default_success(self): + """ + Testing get a reported event + """ + + request, channel = self.make_request( + "GET", self.url, access_token=self.admin_user_tok, + ) + self.render(request) + + self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) + self._check_fields(channel.json_body) + + def test_invalid_report_id(self): + """ + Testing that a invalid `report_id` returns a 400. + """ + + # `report_id` is negative + request, channel = self.make_request( + "GET", + "/_synapse/admin/v1/event_reports/-123", + access_token=self.admin_user_tok, + ) + self.render(request) + + self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) + self.assertEqual( + "The report_id parameter must be a positive integer.", + channel.json_body["error"], + ) + + # `report_id` is a string + request, channel = self.make_request( + "GET", + "/_synapse/admin/v1/event_reports/abcdef", + access_token=self.admin_user_tok, + ) + self.render(request) + + self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) + self.assertEqual( + "The report_id parameter must be a positive integer.", + channel.json_body["error"], + ) + + # `report_id` is undefinied + request, channel = self.make_request( + "GET", + "/_synapse/admin/v1/event_reports/", + access_token=self.admin_user_tok, + ) + self.render(request) + + self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) + self.assertEqual( + "The report_id parameter must be a positive integer.", + channel.json_body["error"], + ) + + def test_report_id_not_found(self): + """ + Testing that a not existing `report_id` returns a 404. + """ + + request, channel = self.make_request( + "GET", + "/_synapse/admin/v1/event_reports/123", + access_token=self.admin_user_tok, + ) + self.render(request) + + self.assertEqual(404, int(channel.result["code"]), msg=channel.result["body"]) + self.assertEqual(Codes.NOT_FOUND, channel.json_body["errcode"]) + self.assertEqual("Event report not found", channel.json_body["error"]) + + def _create_event_and_report(self, room_id, user_tok): + """Create and report events + """ + resp = self.helper.send(room_id, tok=user_tok) + event_id = resp["event_id"] + + request, channel = self.make_request( + "POST", + "rooms/%s/report/%s" % (room_id, event_id), + json.dumps({"score": -100, "reason": "this makes me sad"}), + access_token=user_tok, + ) + self.render(request) + self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) + def _check_fields(self, content): """Checks that all attributes are present in a event report """ From c672026eb196ba6e7b3c097dca0a149d916a1c95 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Wed, 14 Oct 2020 10:15:59 +0200 Subject: [PATCH 2/9] add newsfile --- changelog.d/8539.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/8539.feature diff --git a/changelog.d/8539.feature b/changelog.d/8539.feature new file mode 100644 index 000000000000..b463591a109a --- /dev/null +++ b/changelog.d/8539.feature @@ -0,0 +1 @@ +Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) in detail und list view. Contributed by @dklimpel. \ No newline at end of file From 8bc62ee58c05ded357bb3f3d5f6d38d1a93812d7 Mon Sep 17 00:00:00 2001 From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com> Date: Thu, 15 Oct 2020 16:06:19 +0200 Subject: [PATCH 3/9] Apply suggestions from code review Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> --- changelog.d/8539.feature | 2 +- docs/admin_api/event_reports.rst | 14 ++++---------- synapse/rest/admin/event_reports.py | 8 ++++---- synapse/storage/databases/main/room.py | 11 ++++++++--- tests/rest/admin/test_event_reports.py | 10 +++++----- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/changelog.d/8539.feature b/changelog.d/8539.feature index b463591a109a..e8f534c2f7d5 100644 --- a/changelog.d/8539.feature +++ b/changelog.d/8539.feature @@ -1 +1 @@ -Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) in detail und list view. Contributed by @dklimpel. \ No newline at end of file +Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) into detail and list endpoints. Contributed by @dklimpel. diff --git a/docs/admin_api/event_reports.rst b/docs/admin_api/event_reports.rst index 8d80fe8ca604..7238aac93a46 100644 --- a/docs/admin_api/event_reports.rst +++ b/docs/admin_api/event_reports.rst @@ -92,7 +92,7 @@ The following fields are returned in the JSON response body: Show details of a specific event ================================ -This API returns information about a specific reported event. +This API returns information about a specific event report. The api is:: @@ -106,7 +106,7 @@ It returns a JSON body like the following: .. code:: jsonc { - "content": { + "content": { "reason": "foo", "score": -100 }, @@ -153,15 +153,9 @@ It returns a JSON body like the following: "user_id": "@foo:matrix.org" } -To paginate, check for ``next_token`` and if present, call the endpoint again -with ``from`` set to the value of ``next_token``. This will return a new page. - -If the endpoint does not return a ``next_token`` then there are no more -reports to paginate through. - **URL parameters:** -- ``report_id``: string - The ID of the reported event. +- ``report_id``: string - The ID of the event report. **Response** @@ -173,7 +167,7 @@ The following fields are returned in the JSON response body: - ``event_id``: string - The ID of the reported event. - ``user_id``: string - This is the user who reported the event and wrote the reason. - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. -- ``content``: object - Content of reported event. +- ``content``: object - Content of the event report. - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. - ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". diff --git a/synapse/rest/admin/event_reports.py b/synapse/rest/admin/event_reports.py index d017a3ababe5..5b89ad8e9d8a 100644 --- a/synapse/rest/admin/event_reports.py +++ b/synapse/rest/admin/event_reports.py @@ -90,7 +90,7 @@ async def on_GET(self, request): class EventReportDetailRestServlet(RestServlet): """ - Get a specific reported event that are known to the homeserver. Results are returned + Get a specific reported event that is known to the homeserver. Results are returned in a dictionary containing report information. The requester must have administrator access in Synapse. @@ -99,9 +99,9 @@ class EventReportDetailRestServlet(RestServlet): 200 OK with details report if success otherwise an error. Args: - The parameter `report_id` is the ID of reported event in database. + The parameter `report_id` is the ID of the event report in the database. Returns: - json list of information from event report + JSON blob of information about the event report """ PATTERNS = admin_patterns("/event_reports/(?P[^/]*)$") @@ -114,7 +114,7 @@ def __init__(self, hs): async def on_GET(self, request, report_id): await assert_requester_is_admin(self.auth, request) - message = "The report_id parameter must be a positive integer." + message = "The report_id parameter must be a string representing a positive integer." try: report_id = int(report_id) except Exception: diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index 36dda061a870..a1b841449ad2 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1411,8 +1411,8 @@ async def add_event_report( desc="add_event_report", ) - async def get_event_report(self, report_id: int) -> Dict[str, Any]: - """Retrieve a an event report + async def get_event_report(self, report_id: int) -> Optional[Dict[str, Any]]: + """Retrieve an event report Args: report_id: ID of reported event in database @@ -1445,7 +1445,12 @@ def _get_event_report_txn(txn, report_id): """ txn.execute(sql, [report_id]) - event_report = self.db_pool.cursor_to_dict(txn) + rows = self.db_pool.cursor_to_dict(txn) + + if not rows: + return None + + event_report = rows[0] try: event_report["content"] = db_to_json(event_report["content"]) diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py index d45769ccbe6a..a39dd69437da 100644 --- a/tests/rest/admin/test_event_reports.py +++ b/tests/rest/admin/test_event_reports.py @@ -72,7 +72,7 @@ def prepare(self, reactor, clock, hs): def test_no_auth(self): """ - Try to get event report without authentication. + Try to get an event report without authentication. """ request, channel = self.make_request("GET", self.url, b"{}") self.render(request) @@ -370,7 +370,7 @@ def _create_event_and_report(self, room_id, user_tok): self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) def _check_fields(self, content): - """Checks that all attributes are present in a event report + """Checks that all attributes are present in an event report """ for c in content: self.assertIn("id", c) @@ -453,7 +453,7 @@ def test_default_success(self): def test_invalid_report_id(self): """ - Testing that a invalid `report_id` returns a 400. + Testing that an invalid `report_id` returns a 400. """ # `report_id` is negative @@ -471,7 +471,7 @@ def test_invalid_report_id(self): channel.json_body["error"], ) - # `report_id` is a string + # `report_id` is a non-numerical string request, channel = self.make_request( "GET", "/_synapse/admin/v1/event_reports/abcdef", @@ -486,7 +486,7 @@ def test_invalid_report_id(self): channel.json_body["error"], ) - # `report_id` is undefinied + # `report_id` is undefined request, channel = self.make_request( "GET", "/_synapse/admin/v1/event_reports/", From 11db6dad14fcdbd6a05c143b6703eaffae0f3299 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Thu, 15 Oct 2020 16:15:10 +0200 Subject: [PATCH 4/9] lint --- synapse/rest/admin/event_reports.py | 4 +++- synapse/storage/databases/main/room.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/synapse/rest/admin/event_reports.py b/synapse/rest/admin/event_reports.py index 5b89ad8e9d8a..6812b0213155 100644 --- a/synapse/rest/admin/event_reports.py +++ b/synapse/rest/admin/event_reports.py @@ -114,7 +114,9 @@ def __init__(self, hs): async def on_GET(self, request, report_id): await assert_requester_is_admin(self.auth, request) - message = "The report_id parameter must be a string representing a positive integer." + message = ( + "The report_id parameter must be a string representing a positive integer." + ) try: report_id = int(report_id) except Exception: diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index a1b841449ad2..0cce82bd0f73 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1446,10 +1446,10 @@ def _get_event_report_txn(txn, report_id): txn.execute(sql, [report_id]) rows = self.db_pool.cursor_to_dict(txn) - + if not rows: return None - + event_report = rows[0] try: From b019eed2217ede1155435959c9d0ecd727e0f269 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Thu, 15 Oct 2020 23:54:42 +0200 Subject: [PATCH 5/9] Update newsfile, documentation, tests and database functions Also fixes #8556 --- changelog.d/8539.feature | 2 +- docs/admin_api/event_reports.rst | 42 ++++++---------- synapse/rest/admin/event_reports.py | 2 +- synapse/storage/databases/main/room.py | 66 ++++++++++++++++---------- tests/rest/admin/test_event_reports.py | 49 +++++++++---------- 5 files changed, 81 insertions(+), 80 deletions(-) diff --git a/changelog.d/8539.feature b/changelog.d/8539.feature index e8f534c2f7d5..afb684b95952 100644 --- a/changelog.d/8539.feature +++ b/changelog.d/8539.feature @@ -1 +1 @@ -Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) into detail and list endpoints. Contributed by @dklimpel. +Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) into detail and list endpoints. This is a breaking change to #8217 which was introduced in synapse v1.21.0. Those who already use this API should check their scripts. Contributed by @dklimpel. \ No newline at end of file diff --git a/docs/admin_api/event_reports.rst b/docs/admin_api/event_reports.rst index 7238aac93a46..dc8b2fe0c378 100644 --- a/docs/admin_api/event_reports.rst +++ b/docs/admin_api/event_reports.rst @@ -17,30 +17,26 @@ It returns a JSON body like the following: { "event_reports": [ { - "content": { - "reason": "foo", - "score": -100 - }, "event_id": "$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY", "id": 2, "reason": "foo", + "score": -100, "received_ts": 1570897107409, - "room_alias": "#alias1:matrix.org", + "canonical_alias": "#alias1:matrix.org", "room_id": "!ERAgBpSOcCCuTJqQPk:matrix.org", + "name": "Matrix HQ", "sender": "@foobar:matrix.org", "user_id": "@foo:matrix.org" }, { - "content": { - "reason": "bar", - "score": -100 - }, "event_id": "$3IcdZsDaN_En-S1DF4EMCy3v4gNRKeOJs8W5qTOKj4I", "id": 3, "reason": "bar", + "score": -100, "received_ts": 1598889612059, - "room_alias": "#alias2:matrix.org", + "canonical_alias": "#alias2:matrix.org", "room_id": "!eGvUQuTCkHGVwNMOjv:matrix.org", + "name": "Your room name here", "sender": "@foobar:matrix.org", "user_id": "@bar:matrix.org" } @@ -76,16 +72,13 @@ The following fields are returned in the JSON response body: - ``id``: integer - ID of event report. - ``received_ts``: integer - The timestamp (in milliseconds since the unix epoch) when this report was sent. - ``room_id``: string - The ID of the room in which the event being reported is located. +- ``name``: string - The name of the room. - ``event_id``: string - The ID of the reported event. - ``user_id``: string - This is the user who reported the event and wrote the reason. - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. -- ``content``: object - Content of reported event. - - - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. - - ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". - +- ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". - ``sender``: string - This is the ID of the user who sent the original message/event that was reported. -- ``room_alias``: string - The alias of the room. ``null`` if the room does not have a canonical alias set. +- ``canonical_alias``: string - The canonical alias of the room. ``null`` if the room does not have a canonical alias set. - ``next_token``: integer - Indication for pagination. See above. - ``total``: integer - Total number of event reports related to the query (``user_id`` and ``room_id``). @@ -106,10 +99,6 @@ It returns a JSON body like the following: .. code:: jsonc { - "content": { - "reason": "foo", - "score": -100 - }, "event_id": "$bNUFCwGzWca1meCGkjp-zwslF-GfVcXukvRLI1_FaVY", "event_json": { "auth_events": [ @@ -146,9 +135,11 @@ It returns a JSON body like the following: }, "id": , "reason": "foo", + "score": -100, "received_ts": 1570897107409, - "room_alias": "#alias1:matrix.org", + "canonical_alias": "#alias1:matrix.org", "room_id": "!ERAgBpSOcCCuTJqQPk:matrix.org", + "name": "Matrix HQ", "sender": "@foobar:matrix.org", "user_id": "@foo:matrix.org" } @@ -164,14 +155,11 @@ The following fields are returned in the JSON response body: - ``id``: integer - ID of event report. - ``received_ts``: integer - The timestamp (in milliseconds since the unix epoch) when this report was sent. - ``room_id``: string - The ID of the room in which the event being reported is located. +- ``name``: string - The name of the room. - ``event_id``: string - The ID of the reported event. - ``user_id``: string - This is the user who reported the event and wrote the reason. - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. -- ``content``: object - Content of the event report. - - - ``reason``: string - Comment made by the ``user_id`` in this report. May be blank. - - ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". - +- ``score``: integer - Content is reported based upon a negative score, where -100 is "most offensive" and 0 is "inoffensive". - ``sender``: string - This is the ID of the user who sent the original message/event that was reported. -- ``room_alias``: string - The alias of the room. ``null`` if the room does not have a canonical alias set. +- ``canonical_alias``: string - The canonical alias of the room. ``null`` if the room does not have a canonical alias set. - ``event_json``: object - Details of the original event that was reported. diff --git a/synapse/rest/admin/event_reports.py b/synapse/rest/admin/event_reports.py index 6812b0213155..fd482f0e32d8 100644 --- a/synapse/rest/admin/event_reports.py +++ b/synapse/rest/admin/event_reports.py @@ -119,7 +119,7 @@ async def on_GET(self, request, report_id): ) try: report_id = int(report_id) - except Exception: + except ValueError: raise SynapseError(400, message, errcode=Codes.INVALID_PARAM) if report_id < 0: diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index 0cce82bd0f73..333f43367285 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1429,34 +1429,40 @@ def _get_event_report_txn(txn, report_id): er.room_id, er.event_id, er.user_id, - er.reason, er.content, events.sender, - room_aliases.room_alias, + room_stats_state.canonical_alias, + room_stats_state.name, event_json.json AS event_json FROM event_reports AS er - LEFT JOIN room_aliases - ON room_aliases.room_id = er.room_id - JOIN events + LEFT JOIN events ON events.event_id = er.event_id JOIN event_json ON event_json.event_id = er.event_id + JOIN room_stats_state + ON room_stats_state.room_id = er.room_id WHERE er.id = ? """ txn.execute(sql, [report_id]) - rows = self.db_pool.cursor_to_dict(txn) + row = txn.fetchone() - if not rows: + if not row: return None - event_report = rows[0] - - try: - event_report["content"] = db_to_json(event_report["content"]) - event_report["event_json"] = db_to_json(event_report["event_json"]) - except Exception: - pass + event_report = { + "id": row[0], + "received_ts": row[1], + "room_id": row[2], + "event_id": row[3], + "user_id": row[4], + "score": db_to_json(row[5]).get("score"), + "reason": db_to_json(row[5]).get("reason"), + "sender": row[6], + "canonical_alias": row[7], + "name": row[8], + "event_json": db_to_json(row[9]), + } return event_report @@ -1521,15 +1527,15 @@ def _get_event_reports_paginate_txn(txn): er.room_id, er.event_id, er.user_id, - er.reason, er.content, events.sender, - room_aliases.room_alias + room_stats_state.canonical_alias, + room_stats_state.name FROM event_reports AS er - LEFT JOIN room_aliases - ON room_aliases.room_id = er.room_id - JOIN events + LEFT JOIN events ON events.event_id = er.event_id + JOIN room_stats_state + ON room_stats_state.room_id = er.room_id {where_clause} ORDER BY er.received_ts {order} LIMIT ? @@ -1540,14 +1546,24 @@ def _get_event_reports_paginate_txn(txn): args += [limit, start] txn.execute(sql, args) - event_reports = self.db_pool.cursor_to_dict(txn) + event_reports = [] if count > 0: - for row in event_reports: - try: - row["content"] = db_to_json(row["content"]) - except Exception: - continue + for row in txn: + event_reports.append( + { + "id": row[0], + "received_ts": row[1], + "room_id": row[2], + "event_id": row[3], + "user_id": row[4], + "score": db_to_json(row[5]).get("score"), + "reason": db_to_json(row[5]).get("reason"), + "sender": row[6], + "canonical_alias": row[7], + "name": row[8], + } + ) return event_reports, count diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py index a39dd69437da..1967a15e293c 100644 --- a/tests/rest/admin/test_event_reports.py +++ b/tests/rest/admin/test_event_reports.py @@ -378,12 +378,11 @@ def _check_fields(self, content): self.assertIn("room_id", c) self.assertIn("event_id", c) self.assertIn("user_id", c) - self.assertIn("reason", c) - self.assertIn("content", c) self.assertIn("sender", c) - self.assertIn("room_alias", c) - self.assertIn("score", c["content"]) - self.assertIn("reason", c["content"]) + self.assertIn("canonical_alias", c) + self.assertIn("name", c) + self.assertIn("score", c) + self.assertIn("reason", c) class EventReportDetailTestCase(unittest.HomeserverTestCase): @@ -467,7 +466,7 @@ def test_invalid_report_id(self): self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) self.assertEqual( - "The report_id parameter must be a positive integer.", + "The report_id parameter must be a string representing a positive integer.", channel.json_body["error"], ) @@ -482,7 +481,7 @@ def test_invalid_report_id(self): self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) self.assertEqual( - "The report_id parameter must be a positive integer.", + "The report_id parameter must be a string representing a positive integer.", channel.json_body["error"], ) @@ -497,7 +496,7 @@ def test_invalid_report_id(self): self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) self.assertEqual(Codes.INVALID_PARAM, channel.json_body["errcode"]) self.assertEqual( - "The report_id parameter must be a positive integer.", + "The report_id parameter must be a string representing a positive integer.", channel.json_body["error"], ) @@ -535,21 +534,19 @@ def _create_event_and_report(self, room_id, user_tok): def _check_fields(self, content): """Checks that all attributes are present in a event report """ - for c in content: - self.assertIn("id", c) - self.assertIn("received_ts", c) - self.assertIn("room_id", c) - self.assertIn("event_id", c) - self.assertIn("user_id", c) - self.assertIn("reason", c) - self.assertIn("content", c) - self.assertIn("sender", c) - self.assertIn("room_alias", c) - self.assertIn("event_json", c) - self.assertIn("score", c["content"]) - self.assertIn("reason", c["content"]) - self.assertIn("auth_events", c["event_json"]) - self.assertIn("type", c["event_json"]) - self.assertIn("room_id", c["event_json"]) - self.assertIn("sender", c["event_json"]) - self.assertIn("content", c["event_json"]) + self.assertIn("id", content) + self.assertIn("received_ts", content) + self.assertIn("room_id", content) + self.assertIn("event_id", content) + self.assertIn("user_id", content) + self.assertIn("sender", content) + self.assertIn("canonical_alias", content) + self.assertIn("name", content) + self.assertIn("event_json", content) + self.assertIn("score", content) + self.assertIn("reason", content) + self.assertIn("auth_events", content["event_json"]) + self.assertIn("type", content["event_json"]) + self.assertIn("room_id", content["event_json"]) + self.assertIn("sender", content["event_json"]) + self.assertIn("content", content["event_json"]) From cbdc99815e1280fc42830af42826811b4bf5e44e Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Mon, 19 Oct 2020 22:39:20 +0200 Subject: [PATCH 6/9] Typo an add exception handling --- changelog.d/8539.feature | 2 +- synapse/storage/databases/main/room.py | 37 +++++++++++++++----------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/changelog.d/8539.feature b/changelog.d/8539.feature index afb684b95952..15ce02fb861a 100644 --- a/changelog.d/8539.feature +++ b/changelog.d/8539.feature @@ -1 +1 @@ -Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) into detail and list endpoints. This is a breaking change to #8217 which was introduced in synapse v1.21.0. Those who already use this API should check their scripts. Contributed by @dklimpel. \ No newline at end of file +Split admin API for reported events (`GET /_synapse/admin/v1/event_reports`) into detail and list endpoints. This is a breaking change to #8217 which was introduced in Synapse v1.21.0. Those who already use this API should check their scripts. Contributed by @dklimpel. \ No newline at end of file diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index 333f43367285..de64b0785d83 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1548,22 +1548,29 @@ def _get_event_reports_paginate_txn(txn): txn.execute(sql, args) event_reports = [] - if count > 0: - for row in txn: - event_reports.append( - { - "id": row[0], - "received_ts": row[1], - "room_id": row[2], - "event_id": row[3], - "user_id": row[4], - "score": db_to_json(row[5]).get("score"), - "reason": db_to_json(row[5]).get("reason"), - "sender": row[6], - "canonical_alias": row[7], - "name": row[8], - } + for row in txn: + try: + s = db_to_json(row[5]).get("score") + r = db_to_json(row[5]).get("reason") + except Exception: + logger.error( + "Unable to parse json from event_reports: %s", row[0] ) + continue + event_reports.append( + { + "id": row[0], + "received_ts": row[1], + "room_id": row[2], + "event_id": row[3], + "user_id": row[4], + "score": s, + "reason": r, + "sender": row[6], + "canonical_alias": row[7], + "name": row[8], + } + ) return event_reports, count From 2ae150e28b648b33ada0d91bb7f96a356e23d7a3 Mon Sep 17 00:00:00 2001 From: dklimpel <5740567+dklimpel@users.noreply.github.com> Date: Mon, 19 Oct 2020 22:44:04 +0200 Subject: [PATCH 7/9] lint --- synapse/storage/databases/main/room.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/synapse/storage/databases/main/room.py b/synapse/storage/databases/main/room.py index de64b0785d83..dc0c4b549958 100644 --- a/synapse/storage/databases/main/room.py +++ b/synapse/storage/databases/main/room.py @@ -1553,9 +1553,7 @@ def _get_event_reports_paginate_txn(txn): s = db_to_json(row[5]).get("score") r = db_to_json(row[5]).get("reason") except Exception: - logger.error( - "Unable to parse json from event_reports: %s", row[0] - ) + logger.error("Unable to parse json from event_reports: %s", row[0]) continue event_reports.append( { From dfb36a2f2b895ed507bf171d33b0b184b0cc747b Mon Sep 17 00:00:00 2001 From: Dirk Klimpel <5740567+dklimpel@users.noreply.github.com> Date: Wed, 21 Oct 2020 10:11:51 +0200 Subject: [PATCH 8/9] Fix small typo in comment --- tests/rest/admin/test_event_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rest/admin/test_event_reports.py b/tests/rest/admin/test_event_reports.py index 1967a15e293c..303622217fca 100644 --- a/tests/rest/admin/test_event_reports.py +++ b/tests/rest/admin/test_event_reports.py @@ -276,7 +276,7 @@ def test_invalid_search_order(self): def test_limit_is_negative(self): """ - Testing that a negative list parameter returns a 400 + Testing that a negative limit parameter returns a 400 """ request, channel = self.make_request( From 29e6d83f0915be7e9128b2ca98033b9c2d2ab87f Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Mon, 26 Oct 2020 18:14:20 +0000 Subject: [PATCH 9/9] Update docs/admin_api/event_reports.rst --- docs/admin_api/event_reports.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/admin_api/event_reports.rst b/docs/admin_api/event_reports.rst index dc8b2fe0c378..5f7b0fa6bb02 100644 --- a/docs/admin_api/event_reports.rst +++ b/docs/admin_api/event_reports.rst @@ -82,8 +82,8 @@ The following fields are returned in the JSON response body: - ``next_token``: integer - Indication for pagination. See above. - ``total``: integer - Total number of event reports related to the query (``user_id`` and ``room_id``). -Show details of a specific event -================================ +Show details of a specific event report +======================================= This API returns information about a specific event report.