diff options
Diffstat (limited to 'synapse/rest')
-rw-r--r-- | synapse/rest/admin/__init__.py | 25 | ||||
-rw-r--r-- | synapse/rest/admin/_base.py | 3 | ||||
-rw-r--r-- | synapse/rest/admin/devices.py | 21 | ||||
-rw-r--r-- | synapse/rest/admin/event_reports.py | 21 | ||||
-rw-r--r-- | synapse/rest/admin/federation.py | 135 | ||||
-rw-r--r-- | synapse/rest/admin/groups.py | 5 | ||||
-rw-r--r-- | synapse/rest/admin/media.py | 53 | ||||
-rw-r--r-- | synapse/rest/admin/registration_tokens.py | 51 | ||||
-rw-r--r-- | synapse/rest/admin/rooms.py | 84 | ||||
-rw-r--r-- | synapse/rest/admin/server_notice_servlet.py | 11 | ||||
-rw-r--r-- | synapse/rest/admin/statistics.py | 21 | ||||
-rw-r--r-- | synapse/rest/admin/users.py | 173 | ||||
-rw-r--r-- | synapse/rest/client/login.py | 88 | ||||
-rw-r--r-- | synapse/rest/client/register.py | 20 | ||||
-rw-r--r-- | synapse/rest/client/relations.py | 16 | ||||
-rw-r--r-- | synapse/rest/client/room.py | 67 | ||||
-rw-r--r-- | synapse/rest/client/sync.py | 6 | ||||
-rw-r--r-- | synapse/rest/media/v1/filepath.py | 115 |
18 files changed, 290 insertions, 625 deletions
diff --git a/synapse/rest/admin/__init__.py b/synapse/rest/admin/__init__.py index c499afd4be..ee4a5e481b 100644 --- a/synapse/rest/admin/__init__.py +++ b/synapse/rest/admin/__init__.py @@ -17,7 +17,6 @@ import logging import platform -from http import HTTPStatus from typing import TYPE_CHECKING, Optional, Tuple import synapse @@ -40,10 +39,6 @@ from synapse.rest.admin.event_reports import ( EventReportDetailRestServlet, EventReportsRestServlet, ) -from synapse.rest.admin.federation import ( - DestinationsRestServlet, - ListDestinationsRestServlet, -) from synapse.rest.admin.groups import DeleteGroupAdminRestServlet from synapse.rest.admin.media import ListMediaInRoom, register_servlets_for_media_repo from synapse.rest.admin.registration_tokens import ( @@ -103,7 +98,7 @@ class VersionServlet(RestServlet): } def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: - return HTTPStatus.OK, self.res + return 200, self.res class PurgeHistoryRestServlet(RestServlet): @@ -135,7 +130,7 @@ class PurgeHistoryRestServlet(RestServlet): event = await self.store.get_event(event_id) if event.room_id != room_id: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Event is for wrong room.") + raise SynapseError(400, "Event is for wrong room.") # RoomStreamToken expects [int] not Optional[int] assert event.internal_metadata.stream_ordering is not None @@ -149,9 +144,7 @@ class PurgeHistoryRestServlet(RestServlet): ts = body["purge_up_to_ts"] if not isinstance(ts, int): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "purge_up_to_ts must be an int", - errcode=Codes.BAD_JSON, + 400, "purge_up_to_ts must be an int", errcode=Codes.BAD_JSON ) stream_ordering = await self.store.find_first_stream_ordering_after_ts(ts) @@ -167,9 +160,7 @@ class PurgeHistoryRestServlet(RestServlet): stream_ordering, ) raise SynapseError( - HTTPStatus.NOT_FOUND, - "there is no event to be purged", - errcode=Codes.NOT_FOUND, + 404, "there is no event to be purged", errcode=Codes.NOT_FOUND ) (stream, topo, _event_id) = r token = "t%d-%d" % (topo, stream) @@ -182,7 +173,7 @@ class PurgeHistoryRestServlet(RestServlet): ) else: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "must specify purge_up_to_event_id or purge_up_to_ts", errcode=Codes.BAD_JSON, ) @@ -191,7 +182,7 @@ class PurgeHistoryRestServlet(RestServlet): room_id, token, delete_local_events=delete_local_events ) - return HTTPStatus.OK, {"purge_id": purge_id} + return 200, {"purge_id": purge_id} class PurgeHistoryStatusRestServlet(RestServlet): @@ -210,7 +201,7 @@ class PurgeHistoryStatusRestServlet(RestServlet): if purge_status is None: raise NotFoundError("purge id '%s' not found" % purge_id) - return HTTPStatus.OK, purge_status.asdict() + return 200, purge_status.asdict() ######################################################################################## @@ -265,8 +256,6 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None: ListRegistrationTokensRestServlet(hs).register(http_server) NewRegistrationTokenRestServlet(hs).register(http_server) RegistrationTokenRestServlet(hs).register(http_server) - DestinationsRestServlet(hs).register(http_server) - ListDestinationsRestServlet(hs).register(http_server) # Some servlets only get registered for the main process. if hs.config.worker.worker_app is None: diff --git a/synapse/rest/admin/_base.py b/synapse/rest/admin/_base.py index 399b205aaf..d9a2f6ca15 100644 --- a/synapse/rest/admin/_base.py +++ b/synapse/rest/admin/_base.py @@ -13,7 +13,6 @@ # limitations under the License. import re -from http import HTTPStatus from typing import Iterable, Pattern from synapse.api.auth import Auth @@ -63,4 +62,4 @@ async def assert_user_is_admin(auth: Auth, user_id: UserID) -> None: """ is_admin = await auth.is_server_admin(user_id) if not is_admin: - raise AuthError(HTTPStatus.FORBIDDEN, "You are not a server admin") + raise AuthError(403, "You are not a server admin") diff --git a/synapse/rest/admin/devices.py b/synapse/rest/admin/devices.py index 2e5a6600d3..80fbf32f17 100644 --- a/synapse/rest/admin/devices.py +++ b/synapse/rest/admin/devices.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import NotFoundError, SynapseError @@ -54,7 +53,7 @@ class DeviceRestServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only lookup local users") + raise SynapseError(400, "Can only lookup local users") u = await self.store.get_user_by_id(target_user.to_string()) if u is None: @@ -63,7 +62,7 @@ class DeviceRestServlet(RestServlet): device = await self.device_handler.get_device( target_user.to_string(), device_id ) - return HTTPStatus.OK, device + return 200, device async def on_DELETE( self, request: SynapseRequest, user_id: str, device_id: str @@ -72,14 +71,14 @@ class DeviceRestServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only lookup local users") + raise SynapseError(400, "Can only lookup local users") u = await self.store.get_user_by_id(target_user.to_string()) if u is None: raise NotFoundError("Unknown user") await self.device_handler.delete_device(target_user.to_string(), device_id) - return HTTPStatus.OK, {} + return 200, {} async def on_PUT( self, request: SynapseRequest, user_id: str, device_id: str @@ -88,7 +87,7 @@ class DeviceRestServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only lookup local users") + raise SynapseError(400, "Can only lookup local users") u = await self.store.get_user_by_id(target_user.to_string()) if u is None: @@ -98,7 +97,7 @@ class DeviceRestServlet(RestServlet): await self.device_handler.update_device( target_user.to_string(), device_id, body ) - return HTTPStatus.OK, {} + return 200, {} class DevicesRestServlet(RestServlet): @@ -125,14 +124,14 @@ class DevicesRestServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only lookup local users") + raise SynapseError(400, "Can only lookup local users") u = await self.store.get_user_by_id(target_user.to_string()) if u is None: raise NotFoundError("Unknown user") devices = await self.device_handler.get_devices_by_user(target_user.to_string()) - return HTTPStatus.OK, {"devices": devices, "total": len(devices)} + return 200, {"devices": devices, "total": len(devices)} class DeleteDevicesRestServlet(RestServlet): @@ -156,7 +155,7 @@ class DeleteDevicesRestServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only lookup local users") + raise SynapseError(400, "Can only lookup local users") u = await self.store.get_user_by_id(target_user.to_string()) if u is None: @@ -168,4 +167,4 @@ class DeleteDevicesRestServlet(RestServlet): await self.device_handler.delete_devices( target_user.to_string(), body["devices"] ) - return HTTPStatus.OK, {} + return 200, {} diff --git a/synapse/rest/admin/event_reports.py b/synapse/rest/admin/event_reports.py index 5ee8b11110..bbfcaf723b 100644 --- a/synapse/rest/admin/event_reports.py +++ b/synapse/rest/admin/event_reports.py @@ -13,7 +13,6 @@ # limitations under the License. import logging -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import Codes, NotFoundError, SynapseError @@ -67,23 +66,21 @@ class EventReportsRestServlet(RestServlet): if start < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "The start parameter must be a positive integer.", errcode=Codes.INVALID_PARAM, ) if limit < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "The limit parameter must be a positive integer.", errcode=Codes.INVALID_PARAM, ) if direction not in ("f", "b"): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Unknown direction: %s" % (direction,), - errcode=Codes.INVALID_PARAM, + 400, "Unknown direction: %s" % (direction,), errcode=Codes.INVALID_PARAM ) event_reports, total = await self.store.get_event_reports_paginate( @@ -93,7 +90,7 @@ class EventReportsRestServlet(RestServlet): if (start + limit) < total: ret["next_token"] = start + len(event_reports) - return HTTPStatus.OK, ret + return 200, ret class EventReportDetailRestServlet(RestServlet): @@ -130,17 +127,13 @@ class EventReportDetailRestServlet(RestServlet): try: resolved_report_id = int(report_id) except ValueError: - raise SynapseError( - HTTPStatus.BAD_REQUEST, message, errcode=Codes.INVALID_PARAM - ) + raise SynapseError(400, message, errcode=Codes.INVALID_PARAM) if resolved_report_id < 0: - raise SynapseError( - HTTPStatus.BAD_REQUEST, message, errcode=Codes.INVALID_PARAM - ) + raise SynapseError(400, message, errcode=Codes.INVALID_PARAM) ret = await self.store.get_event_report(resolved_report_id) if not ret: raise NotFoundError("Event report not found") - return HTTPStatus.OK, ret + return 200, ret diff --git a/synapse/rest/admin/federation.py b/synapse/rest/admin/federation.py deleted file mode 100644 index 744687be35..0000000000 --- a/synapse/rest/admin/federation.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright 2021 The Matrix.org Foundation C.I.C. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import logging -from http import HTTPStatus -from typing import TYPE_CHECKING, Tuple - -from synapse.api.errors import Codes, NotFoundError, SynapseError -from synapse.http.servlet import RestServlet, parse_integer, parse_string -from synapse.http.site import SynapseRequest -from synapse.rest.admin._base import admin_patterns, assert_requester_is_admin -from synapse.storage.databases.main.transactions import DestinationSortOrder -from synapse.types import JsonDict - -if TYPE_CHECKING: - from synapse.server import HomeServer - -logger = logging.getLogger(__name__) - - -class ListDestinationsRestServlet(RestServlet): - """Get request to list all destinations. - This needs user to have administrator access in Synapse. - - GET /_synapse/admin/v1/federation/destinations?from=0&limit=10 - - returns: - 200 OK with list of destinations if success otherwise an error. - - The parameters `from` and `limit` are required only for pagination. - By default, a `limit` of 100 is used. - The parameter `destination` can be used to filter by destination. - The parameter `order_by` can be used to order the result. - """ - - PATTERNS = admin_patterns("/federation/destinations$") - - def __init__(self, hs: "HomeServer"): - self._auth = hs.get_auth() - self._store = hs.get_datastore() - - async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]: - await assert_requester_is_admin(self._auth, request) - - start = parse_integer(request, "from", default=0) - limit = parse_integer(request, "limit", default=100) - - if start < 0: - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Query parameter from must be a string representing a positive integer.", - errcode=Codes.INVALID_PARAM, - ) - - if limit < 0: - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Query parameter limit must be a string representing a positive integer.", - errcode=Codes.INVALID_PARAM, - ) - - destination = parse_string(request, "destination") - - order_by = parse_string( - request, - "order_by", - default=DestinationSortOrder.DESTINATION.value, - allowed_values=[dest.value for dest in DestinationSortOrder], - ) - - direction = parse_string(request, "dir", default="f", allowed_values=("f", "b")) - - destinations, total = await self._store.get_destinations_paginate( - start, limit, destination, order_by, direction - ) - response = {"destinations": destinations, "total": total} - if (start + limit) < total: - response["next_token"] = str(start + len(destinations)) - - return HTTPStatus.OK, response - - -class DestinationsRestServlet(RestServlet): - """Get details of a destination. - This needs user to have administrator access in Synapse. - - GET /_synapse/admin/v1/federation/destinations/<destination> - - returns: - 200 OK with details of a destination if success otherwise an error. - """ - - PATTERNS = admin_patterns("/federation/destinations/(?P<destination>[^/]+)$") - - def __init__(self, hs: "HomeServer"): - self._auth = hs.get_auth() - self._store = hs.get_datastore() - - async def on_GET( - self, request: SynapseRequest, destination: str - ) -> Tuple[int, JsonDict]: - await assert_requester_is_admin(self._auth, request) - - destination_retry_timings = await self._store.get_destination_retry_timings( - destination - ) - - if not destination_retry_timings: - raise NotFoundError("Unknown destination") - - last_successful_stream_ordering = ( - await self._store.get_destination_last_successful_stream_ordering( - destination - ) - ) - - response = { - "destination": destination, - "failure_ts": destination_retry_timings.failure_ts, - "retry_last_ts": destination_retry_timings.retry_last_ts, - "retry_interval": destination_retry_timings.retry_interval, - "last_successful_stream_ordering": last_successful_stream_ordering, - } - - return HTTPStatus.OK, response diff --git a/synapse/rest/admin/groups.py b/synapse/rest/admin/groups.py index a27110388f..68a3ba3cb7 100644 --- a/synapse/rest/admin/groups.py +++ b/synapse/rest/admin/groups.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import SynapseError @@ -44,7 +43,7 @@ class DeleteGroupAdminRestServlet(RestServlet): await assert_user_is_admin(self.auth, requester.user) if not self.is_mine_id(group_id): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only delete local groups") + raise SynapseError(400, "Can only delete local groups") await self.group_server.delete_group(group_id, requester.user.to_string()) - return HTTPStatus.OK, {} + return 200, {} diff --git a/synapse/rest/admin/media.py b/synapse/rest/admin/media.py index 9e23e2d8fc..30a687d234 100644 --- a/synapse/rest/admin/media.py +++ b/synapse/rest/admin/media.py @@ -14,7 +14,6 @@ # limitations under the License. import logging -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError @@ -63,7 +62,7 @@ class QuarantineMediaInRoom(RestServlet): room_id, requester.user.to_string() ) - return HTTPStatus.OK, {"num_quarantined": num_quarantined} + return 200, {"num_quarantined": num_quarantined} class QuarantineMediaByUser(RestServlet): @@ -90,7 +89,7 @@ class QuarantineMediaByUser(RestServlet): user_id, requester.user.to_string() ) - return HTTPStatus.OK, {"num_quarantined": num_quarantined} + return 200, {"num_quarantined": num_quarantined} class QuarantineMediaByID(RestServlet): @@ -119,7 +118,7 @@ class QuarantineMediaByID(RestServlet): server_name, media_id, requester.user.to_string() ) - return HTTPStatus.OK, {} + return 200, {} class UnquarantineMediaByID(RestServlet): @@ -148,7 +147,7 @@ class UnquarantineMediaByID(RestServlet): # Remove from quarantine this media id await self.store.quarantine_media_by_id(server_name, media_id, None) - return HTTPStatus.OK, {} + return 200, {} class ProtectMediaByID(RestServlet): @@ -171,7 +170,7 @@ class ProtectMediaByID(RestServlet): # Protect this media id await self.store.mark_local_media_as_safe(media_id, safe=True) - return HTTPStatus.OK, {} + return 200, {} class UnprotectMediaByID(RestServlet): @@ -194,7 +193,7 @@ class UnprotectMediaByID(RestServlet): # Unprotect this media id await self.store.mark_local_media_as_safe(media_id, safe=False) - return HTTPStatus.OK, {} + return 200, {} class ListMediaInRoom(RestServlet): @@ -212,11 +211,11 @@ class ListMediaInRoom(RestServlet): requester = await self.auth.get_user_by_req(request) is_admin = await self.auth.is_server_admin(requester.user) if not is_admin: - raise AuthError(HTTPStatus.FORBIDDEN, "You are not a server admin") + raise AuthError(403, "You are not a server admin") local_mxcs, remote_mxcs = await self.store.get_media_mxcs_in_room(room_id) - return HTTPStatus.OK, {"local": local_mxcs, "remote": remote_mxcs} + return 200, {"local": local_mxcs, "remote": remote_mxcs} class PurgeMediaCacheRestServlet(RestServlet): @@ -234,13 +233,13 @@ class PurgeMediaCacheRestServlet(RestServlet): if before_ts < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter before_ts must be a positive integer.", errcode=Codes.INVALID_PARAM, ) elif before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter before_ts you provided is from the year 1970. " + "Double check that you are providing a timestamp in milliseconds.", errcode=Codes.INVALID_PARAM, @@ -248,7 +247,7 @@ class PurgeMediaCacheRestServlet(RestServlet): ret = await self.media_repository.delete_old_remote_media(before_ts) - return HTTPStatus.OK, ret + return 200, ret class DeleteMediaByID(RestServlet): @@ -268,7 +267,7 @@ class DeleteMediaByID(RestServlet): await assert_requester_is_admin(self.auth, request) if self.server_name != server_name: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only delete local media") + raise SynapseError(400, "Can only delete local media") if await self.store.get_local_media(media_id) is None: raise NotFoundError("Unknown media") @@ -278,7 +277,7 @@ class DeleteMediaByID(RestServlet): deleted_media, total = await self.media_repository.delete_local_media_ids( [media_id] ) - return HTTPStatus.OK, {"deleted_media": deleted_media, "total": total} + return 200, {"deleted_media": deleted_media, "total": total} class DeleteMediaByDateSize(RestServlet): @@ -305,26 +304,26 @@ class DeleteMediaByDateSize(RestServlet): if before_ts < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter before_ts must be a positive integer.", errcode=Codes.INVALID_PARAM, ) elif before_ts < 30000000000: # Dec 1970 in milliseconds, Aug 2920 in seconds raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter before_ts you provided is from the year 1970. " + "Double check that you are providing a timestamp in milliseconds.", errcode=Codes.INVALID_PARAM, ) if size_gt < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter size_gt must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) if self.server_name != server_name: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only delete local media") + raise SynapseError(400, "Can only delete local media") logging.info( "Deleting local media by timestamp: %s, size larger than: %s, keep profile media: %s" @@ -334,7 +333,7 @@ class DeleteMediaByDateSize(RestServlet): deleted_media, total = await self.media_repository.delete_old_local_media( before_ts, size_gt, keep_profiles ) - return HTTPStatus.OK, {"deleted_media": deleted_media, "total": total} + return 200, {"deleted_media": deleted_media, "total": total} class UserMediaRestServlet(RestServlet): @@ -370,7 +369,7 @@ class UserMediaRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.is_mine(UserID.from_string(user_id)): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") + raise SynapseError(400, "Can only look up local users") user = await self.store.get_user_by_id(user_id) if user is None: @@ -381,14 +380,14 @@ class UserMediaRestServlet(RestServlet): if start < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter from must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) if limit < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter limit must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -426,7 +425,7 @@ class UserMediaRestServlet(RestServlet): if (start + limit) < total: ret["next_token"] = start + len(media) - return HTTPStatus.OK, ret + return 200, ret async def on_DELETE( self, request: SynapseRequest, user_id: str @@ -437,7 +436,7 @@ class UserMediaRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.is_mine(UserID.from_string(user_id)): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") + raise SynapseError(400, "Can only look up local users") user = await self.store.get_user_by_id(user_id) if user is None: @@ -448,14 +447,14 @@ class UserMediaRestServlet(RestServlet): if start < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter from must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) if limit < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter limit must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -493,7 +492,7 @@ class UserMediaRestServlet(RestServlet): ([row["media_id"] for row in media]) ) - return HTTPStatus.OK, {"deleted_media": deleted_media, "total": total} + return 200, {"deleted_media": deleted_media, "total": total} def register_servlets_for_media_repo(hs: "HomeServer", http_server: HttpServer) -> None: diff --git a/synapse/rest/admin/registration_tokens.py b/synapse/rest/admin/registration_tokens.py index 891b98c088..aba48f6e7b 100644 --- a/synapse/rest/admin/registration_tokens.py +++ b/synapse/rest/admin/registration_tokens.py @@ -14,7 +14,6 @@ import logging import string -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import Codes, NotFoundError, SynapseError @@ -78,7 +77,7 @@ class ListRegistrationTokensRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) valid = parse_boolean(request, "valid") token_list = await self.store.get_registration_tokens(valid) - return HTTPStatus.OK, {"registration_tokens": token_list} + return 200, {"registration_tokens": token_list} class NewRegistrationTokenRestServlet(RestServlet): @@ -124,20 +123,16 @@ class NewRegistrationTokenRestServlet(RestServlet): if "token" in body: token = body["token"] if not isinstance(token, str): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "token must be a string", - Codes.INVALID_PARAM, - ) + raise SynapseError(400, "token must be a string", Codes.INVALID_PARAM) if not (0 < len(token) <= 64): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "token must not be empty and must not be longer than 64 characters", Codes.INVALID_PARAM, ) if not set(token).issubset(self.allowed_chars_set): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "token must consist only of characters matched by the regex [A-Za-z0-9-_]", Codes.INVALID_PARAM, ) @@ -147,13 +142,11 @@ class NewRegistrationTokenRestServlet(RestServlet): length = body.get("length", 16) if not isinstance(length, int): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "length must be an integer", - Codes.INVALID_PARAM, + 400, "length must be an integer", Codes.INVALID_PARAM ) if not (0 < length <= 64): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "length must be greater than zero and not greater than 64", Codes.INVALID_PARAM, ) @@ -169,7 +162,7 @@ class NewRegistrationTokenRestServlet(RestServlet): or (isinstance(uses_allowed, int) and uses_allowed >= 0) ): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "uses_allowed must be a non-negative integer or null", Codes.INVALID_PARAM, ) @@ -177,15 +170,11 @@ class NewRegistrationTokenRestServlet(RestServlet): expiry_time = body.get("expiry_time", None) if not isinstance(expiry_time, (int, type(None))): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "expiry_time must be an integer or null", - Codes.INVALID_PARAM, + 400, "expiry_time must be an integer or null", Codes.INVALID_PARAM ) if isinstance(expiry_time, int) and expiry_time < self.clock.time_msec(): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "expiry_time must not be in the past", - Codes.INVALID_PARAM, + 400, "expiry_time must not be in the past", Codes.INVALID_PARAM ) created = await self.store.create_registration_token( @@ -193,9 +182,7 @@ class NewRegistrationTokenRestServlet(RestServlet): ) if not created: raise SynapseError( - HTTPStatus.BAD_REQUEST, - f"Token already exists: {token}", - Codes.INVALID_PARAM, + 400, f"Token already exists: {token}", Codes.INVALID_PARAM ) resp = { @@ -205,7 +192,7 @@ class NewRegistrationTokenRestServlet(RestServlet): "completed": 0, "expiry_time": expiry_time, } - return HTTPStatus.OK, resp + return 200, resp class RegistrationTokenRestServlet(RestServlet): @@ -274,7 +261,7 @@ class RegistrationTokenRestServlet(RestServlet): if token_info is None: raise NotFoundError(f"No such registration token: {token}") - return HTTPStatus.OK, token_info + return 200, token_info async def on_PUT(self, request: SynapseRequest, token: str) -> Tuple[int, JsonDict]: """Update a registration token.""" @@ -290,7 +277,7 @@ class RegistrationTokenRestServlet(RestServlet): or (isinstance(uses_allowed, int) and uses_allowed >= 0) ): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "uses_allowed must be a non-negative integer or null", Codes.INVALID_PARAM, ) @@ -300,15 +287,11 @@ class RegistrationTokenRestServlet(RestServlet): expiry_time = body["expiry_time"] if not isinstance(expiry_time, (int, type(None))): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "expiry_time must be an integer or null", - Codes.INVALID_PARAM, + 400, "expiry_time must be an integer or null", Codes.INVALID_PARAM ) if isinstance(expiry_time, int) and expiry_time < self.clock.time_msec(): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "expiry_time must not be in the past", - Codes.INVALID_PARAM, + 400, "expiry_time must not be in the past", Codes.INVALID_PARAM ) new_attributes["expiry_time"] = expiry_time @@ -324,7 +307,7 @@ class RegistrationTokenRestServlet(RestServlet): if token_info is None: raise NotFoundError(f"No such registration token: {token}") - return HTTPStatus.OK, token_info + return 200, token_info async def on_DELETE( self, request: SynapseRequest, token: str @@ -333,6 +316,6 @@ class RegistrationTokenRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if await self.store.delete_registration_token(token): - return HTTPStatus.OK, {} + return 200, {} raise NotFoundError(f"No such registration token: {token}") diff --git a/synapse/rest/admin/rooms.py b/synapse/rest/admin/rooms.py index 829e86675a..a89dda1ba5 100644 --- a/synapse/rest/admin/rooms.py +++ b/synapse/rest/admin/rooms.py @@ -102,9 +102,10 @@ class RoomRestV2Servlet(RestServlet): ) if not RoomID.is_valid(room_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "%s is not a legal room ID" % (room_id,) - ) + raise SynapseError(400, "%s is not a legal room ID" % (room_id,)) + + if not await self._store.get_room(room_id): + raise NotFoundError("Unknown room id %s" % (room_id,)) delete_id = self._pagination_handler.start_shutdown_and_purge_room( room_id=room_id, @@ -117,7 +118,7 @@ class RoomRestV2Servlet(RestServlet): force_purge=force_purge, ) - return HTTPStatus.OK, {"delete_id": delete_id} + return 200, {"delete_id": delete_id} class DeleteRoomStatusByRoomIdRestServlet(RestServlet): @@ -136,9 +137,7 @@ class DeleteRoomStatusByRoomIdRestServlet(RestServlet): await assert_requester_is_admin(self._auth, request) if not RoomID.is_valid(room_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "%s is not a legal room ID" % (room_id,) - ) + raise SynapseError(400, "%s is not a legal room ID" % (room_id,)) delete_ids = self._pagination_handler.get_delete_ids_by_room(room_id) if delete_ids is None: @@ -154,7 +153,7 @@ class DeleteRoomStatusByRoomIdRestServlet(RestServlet): **delete.asdict(), } ] - return HTTPStatus.OK, {"results": cast(JsonDict, response)} + return 200, {"results": cast(JsonDict, response)} class DeleteRoomStatusByDeleteIdRestServlet(RestServlet): @@ -176,7 +175,7 @@ class DeleteRoomStatusByDeleteIdRestServlet(RestServlet): if delete_status is None: raise NotFoundError("delete id '%s' not found" % delete_id) - return HTTPStatus.OK, cast(JsonDict, delete_status.asdict()) + return 200, cast(JsonDict, delete_status.asdict()) class ListRoomRestServlet(RestServlet): @@ -218,7 +217,7 @@ class ListRoomRestServlet(RestServlet): RoomSortOrder.STATE_EVENTS.value, ): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Unknown value for order_by: %s" % (order_by,), errcode=Codes.INVALID_PARAM, ) @@ -226,7 +225,7 @@ class ListRoomRestServlet(RestServlet): search_term = parse_string(request, "search_term", encoding="utf-8") if search_term == "": raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "search_term cannot be an empty string", errcode=Codes.INVALID_PARAM, ) @@ -234,9 +233,7 @@ class ListRoomRestServlet(RestServlet): direction = parse_string(request, "dir", default="f") if direction not in ("f", "b"): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Unknown direction: %s" % (direction,), - errcode=Codes.INVALID_PARAM, + 400, "Unknown direction: %s" % (direction,), errcode=Codes.INVALID_PARAM ) reverse_order = True if direction == "b" else False @@ -268,7 +265,7 @@ class ListRoomRestServlet(RestServlet): else: response["prev_batch"] = 0 - return HTTPStatus.OK, response + return 200, response class RoomRestServlet(RestServlet): @@ -313,7 +310,7 @@ class RoomRestServlet(RestServlet): members = await self.store.get_users_in_room(room_id) ret["joined_local_devices"] = await self.store.count_devices_by_users(members) - return HTTPStatus.OK, ret + return 200, ret async def on_DELETE( self, request: SynapseRequest, room_id: str @@ -389,7 +386,7 @@ class RoomRestServlet(RestServlet): # See https://github.com/python/mypy/issues/4976#issuecomment-579883622 # for some discussion on why this is necessary. Either way, # `ret` is an opaque dictionary blob as far as the rest of the app cares. - return HTTPStatus.OK, cast(JsonDict, ret) + return 200, cast(JsonDict, ret) class RoomMembersRestServlet(RestServlet): @@ -416,7 +413,7 @@ class RoomMembersRestServlet(RestServlet): members = await self.store.get_users_in_room(room_id) ret = {"members": members, "total": len(members)} - return HTTPStatus.OK, ret + return 200, ret class RoomStateRestServlet(RestServlet): @@ -446,10 +443,16 @@ class RoomStateRestServlet(RestServlet): event_ids = await self.store.get_current_state_ids(room_id) events = await self.store.get_events(event_ids.values()) now = self.clock.time_msec() - room_state = await self._event_serializer.serialize_events(events.values(), now) + room_state = await self._event_serializer.serialize_events( + events.values(), + now, + # We don't bother bundling aggregations in when asked for state + # events, as clients won't use them. + bundle_relations=False, + ) ret = {"state": room_state} - return HTTPStatus.OK, ret + return 200, ret class JoinRoomAliasServlet(ResolveRoomIdMixin, RestServlet): @@ -478,10 +481,7 @@ class JoinRoomAliasServlet(ResolveRoomIdMixin, RestServlet): target_user = UserID.from_string(content["user_id"]) if not self.hs.is_mine(target_user): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "This endpoint can only be used with local users", - ) + raise SynapseError(400, "This endpoint can only be used with local users") if not await self.admin_handler.get_user(target_user): raise NotFoundError("User not found") @@ -527,7 +527,7 @@ class JoinRoomAliasServlet(ResolveRoomIdMixin, RestServlet): ratelimit=False, ) - return HTTPStatus.OK, {"room_id": room_id} + return 200, {"room_id": room_id} class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): @@ -568,7 +568,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): # Figure out which local users currently have power in the room, if any. room_state = await self.state_handler.get_current_state(room_id) if not room_state: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Server not in room") + raise SynapseError(400, "Server not in room") create_event = room_state[(EventTypes.Create, "")] power_levels = room_state.get((EventTypes.PowerLevels, "")) @@ -582,9 +582,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): admin_users.sort(key=lambda user: user_power[user]) if not admin_users: - raise SynapseError( - HTTPStatus.BAD_REQUEST, "No local admin user in room" - ) + raise SynapseError(400, "No local admin user in room") admin_user_id = None @@ -601,7 +599,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): if not admin_user_id: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "No local admin user in room", ) @@ -612,7 +610,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): admin_user_id = create_event.sender if not self.is_mine_id(admin_user_id): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "No local admin user in room", ) @@ -641,8 +639,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): except AuthError: # The admin user we found turned out not to have enough power. raise SynapseError( - HTTPStatus.BAD_REQUEST, - "No local admin user in room with power to update power levels.", + 400, "No local admin user in room with power to update power levels." ) # Now we check if the user we're granting admin rights to is already in @@ -656,7 +653,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): ) if is_joined: - return HTTPStatus.OK, {} + return 200, {} join_rules = room_state.get((EventTypes.JoinRules, "")) is_public = False @@ -664,7 +661,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): is_public = join_rules.content.get("join_rule") == JoinRules.PUBLIC if is_public: - return HTTPStatus.OK, {} + return 200, {} await self.room_member_handler.update_membership( fake_requester, @@ -673,7 +670,7 @@ class MakeRoomAdminRestServlet(ResolveRoomIdMixin, RestServlet): action=Membership.INVITE, ) - return HTTPStatus.OK, {} + return 200, {} class ForwardExtremitiesRestServlet(ResolveRoomIdMixin, RestServlet): @@ -705,7 +702,7 @@ class ForwardExtremitiesRestServlet(ResolveRoomIdMixin, RestServlet): room_id, _ = await self.resolve_room_id(room_identifier) deleted_count = await self.store.delete_forward_extremities_for_room(room_id) - return HTTPStatus.OK, {"deleted": deleted_count} + return 200, {"deleted": deleted_count} async def on_GET( self, request: SynapseRequest, room_identifier: str @@ -716,7 +713,7 @@ class ForwardExtremitiesRestServlet(ResolveRoomIdMixin, RestServlet): room_id, _ = await self.resolve_room_id(room_identifier) extremities = await self.store.get_forward_extremities_for_room(room_id) - return HTTPStatus.OK, {"count": len(extremities), "results": extremities} + return 200, {"count": len(extremities), "results": extremities} class RoomEventContextServlet(RestServlet): @@ -765,9 +762,7 @@ class RoomEventContextServlet(RestServlet): ) if not results: - raise SynapseError( - HTTPStatus.NOT_FOUND, "Event not found.", errcode=Codes.NOT_FOUND - ) + raise SynapseError(404, "Event not found.", errcode=Codes.NOT_FOUND) time_now = self.clock.time_msec() results["events_before"] = await self._event_serializer.serialize_events( @@ -780,10 +775,13 @@ class RoomEventContextServlet(RestServlet): results["events_after"], time_now ) results["state"] = await self._event_serializer.serialize_events( - results["state"], time_now + results["state"], + time_now, + # No need to bundle aggregations for state events + bundle_relations=False, ) - return HTTPStatus.OK, results + return 200, results class BlockRoomRestServlet(RestServlet): diff --git a/synapse/rest/admin/server_notice_servlet.py b/synapse/rest/admin/server_notice_servlet.py index b295fb078b..19f84f33f2 100644 --- a/synapse/rest/admin/server_notice_servlet.py +++ b/synapse/rest/admin/server_notice_servlet.py @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from http import HTTPStatus from typing import TYPE_CHECKING, Awaitable, Optional, Tuple from synapse.api.constants import EventTypes @@ -83,15 +82,11 @@ class SendServerNoticeServlet(RestServlet): # but worker processes still need to initialise SendServerNoticeServlet (as it is part of the # admin api). if not self.server_notices_manager.is_enabled(): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Server notices are not enabled on this server" - ) + raise SynapseError(400, "Server notices are not enabled on this server") target_user = UserID.from_string(body["user_id"]) if not self.hs.is_mine(target_user): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Server notices can only be sent to local users" - ) + raise SynapseError(400, "Server notices can only be sent to local users") if not await self.admin_handler.get_user(target_user): raise NotFoundError("User not found") @@ -104,7 +99,7 @@ class SendServerNoticeServlet(RestServlet): txn_id=txn_id, ) - return HTTPStatus.OK, {"event_id": event.event_id} + return 200, {"event_id": event.event_id} def on_PUT( self, request: SynapseRequest, txn_id: str diff --git a/synapse/rest/admin/statistics.py b/synapse/rest/admin/statistics.py index ca41fd45f2..948de94ccd 100644 --- a/synapse/rest/admin/statistics.py +++ b/synapse/rest/admin/statistics.py @@ -13,7 +13,6 @@ # limitations under the License. import logging -from http import HTTPStatus from typing import TYPE_CHECKING, Tuple from synapse.api.errors import Codes, SynapseError @@ -54,7 +53,7 @@ class UserMediaStatisticsRestServlet(RestServlet): UserSortOrder.DISPLAYNAME.value, ): raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Unknown value for order_by: %s" % (order_by,), errcode=Codes.INVALID_PARAM, ) @@ -62,7 +61,7 @@ class UserMediaStatisticsRestServlet(RestServlet): start = parse_integer(request, "from", default=0) if start < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter from must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -70,7 +69,7 @@ class UserMediaStatisticsRestServlet(RestServlet): limit = parse_integer(request, "limit", default=100) if limit < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter limit must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -78,7 +77,7 @@ class UserMediaStatisticsRestServlet(RestServlet): from_ts = parse_integer(request, "from_ts", default=0) if from_ts < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter from_ts must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -87,13 +86,13 @@ class UserMediaStatisticsRestServlet(RestServlet): if until_ts is not None: if until_ts < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter until_ts must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) if until_ts <= from_ts: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter until_ts must be greater than from_ts.", errcode=Codes.INVALID_PARAM, ) @@ -101,7 +100,7 @@ class UserMediaStatisticsRestServlet(RestServlet): search_term = parse_string(request, "search_term") if search_term == "": raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter search_term cannot be an empty string.", errcode=Codes.INVALID_PARAM, ) @@ -109,9 +108,7 @@ class UserMediaStatisticsRestServlet(RestServlet): direction = parse_string(request, "dir", default="f") if direction not in ("f", "b"): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Unknown direction: %s" % (direction,), - errcode=Codes.INVALID_PARAM, + 400, "Unknown direction: %s" % (direction,), errcode=Codes.INVALID_PARAM ) users_media, total = await self.store.get_users_media_usage_paginate( @@ -121,4 +118,4 @@ class UserMediaStatisticsRestServlet(RestServlet): if (start + limit) < total: ret["next_token"] = start + len(users_media) - return HTTPStatus.OK, ret + return 200, ret diff --git a/synapse/rest/admin/users.py b/synapse/rest/admin/users.py index 2a60b602b1..ccd9a2a175 100644 --- a/synapse/rest/admin/users.py +++ b/synapse/rest/admin/users.py @@ -79,14 +79,14 @@ class UsersRestServletV2(RestServlet): if start < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter from must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) if limit < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Query parameter limit must be a string representing a positive integer.", errcode=Codes.INVALID_PARAM, ) @@ -122,7 +122,7 @@ class UsersRestServletV2(RestServlet): if (start + limit) < total: ret["next_token"] = str(start + len(users)) - return HTTPStatus.OK, ret + return 200, ret class UserRestServletV2(RestServlet): @@ -172,14 +172,14 @@ class UserRestServletV2(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") + raise SynapseError(400, "Can only look up local users") ret = await self.admin_handler.get_user(target_user) if not ret: raise NotFoundError("User not found") - return HTTPStatus.OK, ret + return 200, ret async def on_PUT( self, request: SynapseRequest, user_id: str @@ -191,10 +191,7 @@ class UserRestServletV2(RestServlet): body = parse_json_object_from_request(request) if not self.hs.is_mine(target_user): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "This endpoint can only be used with local users", - ) + raise SynapseError(400, "This endpoint can only be used with local users") user = await self.admin_handler.get_user(target_user) user_id = target_user.to_string() @@ -213,7 +210,7 @@ class UserRestServletV2(RestServlet): user_type = body.get("user_type", None) if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") + raise SynapseError(400, "Invalid user type") set_admin_to = body.get("admin", False) if not isinstance(set_admin_to, bool): @@ -226,13 +223,11 @@ class UserRestServletV2(RestServlet): password = body.get("password", None) if password is not None: if not isinstance(password, str) or len(password) > 512: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") + raise SynapseError(400, "Invalid password") deactivate = body.get("deactivated", False) if not isinstance(deactivate, bool): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "'deactivated' parameter is not of type boolean" - ) + raise SynapseError(400, "'deactivated' parameter is not of type boolean") # convert List[Dict[str, str]] into List[Tuple[str, str]] if external_ids is not None: @@ -287,9 +282,7 @@ class UserRestServletV2(RestServlet): user_id, ) except ExternalIDReuseException: - raise SynapseError( - HTTPStatus.CONFLICT, "External id is already in use." - ) + raise SynapseError(409, "External id is already in use.") if "avatar_url" in body and isinstance(body["avatar_url"], str): await self.profile_handler.set_avatar_url( @@ -300,9 +293,7 @@ class UserRestServletV2(RestServlet): if set_admin_to != user["admin"]: auth_user = requester.user if target_user == auth_user and not set_admin_to: - raise SynapseError( - HTTPStatus.BAD_REQUEST, "You may not demote yourself." - ) + raise SynapseError(400, "You may not demote yourself.") await self.store.set_server_admin(target_user, set_admin_to) @@ -328,8 +319,7 @@ class UserRestServletV2(RestServlet): and self.auth_handler.can_change_password() ): raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Must provide a password to re-activate an account.", + 400, "Must provide a password to re-activate an account." ) await self.deactivate_account_handler.activate_account( @@ -342,7 +332,7 @@ class UserRestServletV2(RestServlet): user = await self.admin_handler.get_user(target_user) assert user is not None - return HTTPStatus.OK, user + return 200, user else: # create user displayname = body.get("displayname", None) @@ -391,9 +381,7 @@ class UserRestServletV2(RestServlet): user_id, ) except ExternalIDReuseException: - raise SynapseError( - HTTPStatus.CONFLICT, "External id is already in use." - ) + raise SynapseError(409, "External id is already in use.") if "avatar_url" in body and isinstance(body["avatar_url"], str): await self.profile_handler.set_avatar_url( @@ -441,61 +429,51 @@ class UserRegisterServlet(RestServlet): nonce = secrets.token_hex(64) self.nonces[nonce] = int(self.reactor.seconds()) - return HTTPStatus.OK, {"nonce": nonce} + return 200, {"nonce": nonce} async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: self._clear_old_nonces() if not self.hs.config.registration.registration_shared_secret: - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Shared secret registration is not enabled" - ) + raise SynapseError(400, "Shared secret registration is not enabled") body = parse_json_object_from_request(request) if "nonce" not in body: - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "nonce must be specified", - errcode=Codes.BAD_JSON, - ) + raise SynapseError(400, "nonce must be specified", errcode=Codes.BAD_JSON) nonce = body["nonce"] if nonce not in self.nonces: - raise SynapseError(HTTPStatus.BAD_REQUEST, "unrecognised nonce") + raise SynapseError(400, "unrecognised nonce") # Delete the nonce, so it can't be reused, even if it's invalid del self.nonces[nonce] if "username" not in body: raise SynapseError( - HTTPStatus.BAD_REQUEST, - "username must be specified", - errcode=Codes.BAD_JSON, + 400, "username must be specified", errcode=Codes.BAD_JSON ) else: if not isinstance(body["username"], str) or len(body["username"]) > 512: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid username") + raise SynapseError(400, "Invalid username") username = body["username"].encode("utf-8") if b"\x00" in username: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid username") + raise SynapseError(400, "Invalid username") if "password" not in body: raise SynapseError( - HTTPStatus.BAD_REQUEST, - "password must be specified", - errcode=Codes.BAD_JSON, + 400, "password must be specified", errcode=Codes.BAD_JSON ) else: password = body["password"] if not isinstance(password, str) or len(password) > 512: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") + raise SynapseError(400, "Invalid password") password_bytes = password.encode("utf-8") if b"\x00" in password_bytes: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid password") + raise SynapseError(400, "Invalid password") password_hash = await self.auth_handler.hash(password) @@ -504,12 +482,10 @@ class UserRegisterServlet(RestServlet): displayname = body.get("displayname", None) if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: - raise SynapseError(HTTPStatus.BAD_REQUEST, "Invalid user type") + raise SynapseError(400, "Invalid user type") if "mac" not in body: - raise SynapseError( - HTTPStatus.BAD_REQUEST, "mac must be specified", errcode=Codes.BAD_JSON - ) + raise SynapseError(400, "mac must be specified", errcode=Codes.BAD_JSON) got_mac = body["mac"] @@ -531,7 +507,7 @@ class UserRegisterServlet(RestServlet): want_mac = want_mac_builder.hexdigest() if not hmac.compare_digest(want_mac.encode("ascii"), got_mac.encode("ascii")): - raise SynapseError(HTTPStatus.FORBIDDEN, "HMAC incorrect") + raise SynapseError(403, "HMAC incorrect") # Reuse the parts of RegisterRestServlet to reduce code duplication from synapse.rest.client.register import RegisterRestServlet @@ -548,7 +524,7 @@ class UserRegisterServlet(RestServlet): ) result = await register._create_registration_details(user_id, body) - return HTTPStatus.OK, result + return 200, result class WhoisRestServlet(RestServlet): @@ -576,11 +552,11 @@ class WhoisRestServlet(RestServlet): await assert_user_is_admin(self.auth, auth_user) if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only whois a local user") + raise SynapseError(400, "Can only whois a local user") ret = await self.admin_handler.get_whois(target_user) - return HTTPStatus.OK, ret + return 200, ret class DeactivateAccountRestServlet(RestServlet): @@ -599,9 +575,7 @@ class DeactivateAccountRestServlet(RestServlet): await assert_user_is_admin(self.auth, requester.user) if not self.is_mine(UserID.from_string(target_user_id)): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Can only deactivate local users" - ) + raise SynapseError(400, "Can only deactivate local users") if not await self.store.get_user_by_id(target_user_id): raise NotFoundError("User not found") @@ -623,7 +597,7 @@ class DeactivateAccountRestServlet(RestServlet): else: id_server_unbind_result = "no-support" - return HTTPStatus.OK, {"id_server_unbind_result": id_server_unbind_result} + return 200, {"id_server_unbind_result": id_server_unbind_result} class AccountValidityRenewServlet(RestServlet): @@ -646,7 +620,7 @@ class AccountValidityRenewServlet(RestServlet): if "user_id" not in body: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "Missing property 'user_id' in the request body", ) @@ -657,7 +631,7 @@ class AccountValidityRenewServlet(RestServlet): ) res = {"expiration_ts": expiration_ts} - return HTTPStatus.OK, res + return 200, res class ResetPasswordRestServlet(RestServlet): @@ -704,7 +678,7 @@ class ResetPasswordRestServlet(RestServlet): await self._set_password_handler.set_password( target_user_id, new_password_hash, logout_devices, requester ) - return HTTPStatus.OK, {} + return 200, {} class SearchUsersRestServlet(RestServlet): @@ -738,16 +712,16 @@ class SearchUsersRestServlet(RestServlet): # To allow all users to get the users list # if not is_admin and target_user != auth_user: - # raise AuthError(HTTPStatus.FORBIDDEN, "You are not a server admin") + # raise AuthError(403, "You are not a server admin") if not self.hs.is_mine(target_user): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only users a local user") + raise SynapseError(400, "Can only users a local user") term = parse_string(request, "term", required=True) logger.info("term: %s ", term) ret = await self.store.search_users(term) - return HTTPStatus.OK, ret + return 200, ret class UserAdminServlet(RestServlet): @@ -791,14 +765,11 @@ class UserAdminServlet(RestServlet): target_user = UserID.from_string(user_id) if not self.hs.is_mine(target_user): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Only local users can be admins of this homeserver", - ) + raise SynapseError(400, "Only local users can be admins of this homeserver") is_admin = await self.store.is_server_admin(target_user) - return HTTPStatus.OK, {"admin": is_admin} + return 200, {"admin": is_admin} async def on_PUT( self, request: SynapseRequest, user_id: str @@ -814,19 +785,16 @@ class UserAdminServlet(RestServlet): assert_params_in_dict(body, ["admin"]) if not self.hs.is_mine(target_user): - raise SynapseError( - HTTPStatus.BAD_REQUEST, - "Only local users can be admins of this homeserver", - ) + raise SynapseError(400, "Only local users can be admins of this homeserver") set_admin_to = bool(body["admin"]) if target_user == auth_user and not set_admin_to: - raise SynapseError(HTTPStatus.BAD_REQUEST, "You may not demote yourself.") + raise SynapseError(400, "You may not demote yourself.") await self.store.set_server_admin(target_user, set_admin_to) - return HTTPStatus.OK, {} + return 200, {} class UserMembershipRestServlet(RestServlet): @@ -848,7 +816,7 @@ class UserMembershipRestServlet(RestServlet): room_ids = await self.store.get_rooms_for_user(user_id) ret = {"joined_rooms": list(room_ids), "total": len(room_ids)} - return HTTPStatus.OK, ret + return 200, ret class PushersRestServlet(RestServlet): @@ -877,7 +845,7 @@ class PushersRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.is_mine(UserID.from_string(user_id)): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") + raise SynapseError(400, "Can only look up local users") if not await self.store.get_user_by_id(user_id): raise NotFoundError("User not found") @@ -886,10 +854,7 @@ class PushersRestServlet(RestServlet): filtered_pushers = [p.as_dict() for p in pushers] - return HTTPStatus.OK, { - "pushers": filtered_pushers, - "total": len(filtered_pushers), - } + return 200, {"pushers": filtered_pushers, "total": len(filtered_pushers)} class UserTokenRestServlet(RestServlet): @@ -922,22 +887,16 @@ class UserTokenRestServlet(RestServlet): auth_user = requester.user if not self.hs.is_mine_id(user_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Only local users can be logged in as" - ) + raise SynapseError(400, "Only local users can be logged in as") body = parse_json_object_from_request(request, allow_empty_body=True) valid_until_ms = body.get("valid_until_ms") if valid_until_ms and not isinstance(valid_until_ms, int): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "'valid_until_ms' parameter must be an int" - ) + raise SynapseError(400, "'valid_until_ms' parameter must be an int") if auth_user.to_string() == user_id: - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Cannot use admin API to login as self" - ) + raise SynapseError(400, "Cannot use admin API to login as self") token = await self.auth_handler.create_access_token_for_user_id( user_id=auth_user.to_string(), @@ -946,7 +905,7 @@ class UserTokenRestServlet(RestServlet): puppets_user_id=user_id, ) - return HTTPStatus.OK, {"access_token": token} + return 200, {"access_token": token} class ShadowBanRestServlet(RestServlet): @@ -988,13 +947,11 @@ class ShadowBanRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.hs.is_mine_id(user_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Only local users can be shadow-banned" - ) + raise SynapseError(400, "Only local users can be shadow-banned") await self.store.set_shadow_banned(UserID.from_string(user_id), True) - return HTTPStatus.OK, {} + return 200, {} async def on_DELETE( self, request: SynapseRequest, user_id: str @@ -1002,13 +959,11 @@ class ShadowBanRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.hs.is_mine_id(user_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Only local users can be shadow-banned" - ) + raise SynapseError(400, "Only local users can be shadow-banned") await self.store.set_shadow_banned(UserID.from_string(user_id), False) - return HTTPStatus.OK, {} + return 200, {} class RateLimitRestServlet(RestServlet): @@ -1040,7 +995,7 @@ class RateLimitRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.hs.is_mine_id(user_id): - raise SynapseError(HTTPStatus.BAD_REQUEST, "Can only look up local users") + raise SynapseError(400, "Can only look up local users") if not await self.store.get_user_by_id(user_id): raise NotFoundError("User not found") @@ -1061,7 +1016,7 @@ class RateLimitRestServlet(RestServlet): else: ret = {} - return HTTPStatus.OK, ret + return 200, ret async def on_POST( self, request: SynapseRequest, user_id: str @@ -1069,9 +1024,7 @@ class RateLimitRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.hs.is_mine_id(user_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Only local users can be ratelimited" - ) + raise SynapseError(400, "Only local users can be ratelimited") if not await self.store.get_user_by_id(user_id): raise NotFoundError("User not found") @@ -1083,14 +1036,14 @@ class RateLimitRestServlet(RestServlet): if not isinstance(messages_per_second, int) or messages_per_second < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "%r parameter must be a positive int" % (messages_per_second,), errcode=Codes.INVALID_PARAM, ) if not isinstance(burst_count, int) or burst_count < 0: raise SynapseError( - HTTPStatus.BAD_REQUEST, + 400, "%r parameter must be a positive int" % (burst_count,), errcode=Codes.INVALID_PARAM, ) @@ -1106,7 +1059,7 @@ class RateLimitRestServlet(RestServlet): "burst_count": ratelimit.burst_count, } - return HTTPStatus.OK, ret + return 200, ret async def on_DELETE( self, request: SynapseRequest, user_id: str @@ -1114,13 +1067,11 @@ class RateLimitRestServlet(RestServlet): await assert_requester_is_admin(self.auth, request) if not self.hs.is_mine_id(user_id): - raise SynapseError( - HTTPStatus.BAD_REQUEST, "Only local users can be ratelimited" - ) + raise SynapseError(400, "Only local users can be ratelimited") if not await self.store.get_user_by_id(user_id): raise NotFoundError("User not found") await self.store.delete_ratelimit_for_user(user_id) - return HTTPStatus.OK, {} + return 200, {} diff --git a/synapse/rest/client/login.py b/synapse/rest/client/login.py index f9994658c4..67e03dca04 100644 --- a/synapse/rest/client/login.py +++ b/synapse/rest/client/login.py @@ -14,17 +14,7 @@ import logging import re -from typing import ( - TYPE_CHECKING, - Any, - Awaitable, - Callable, - Dict, - List, - Optional, - Tuple, - Union, -) +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Tuple from typing_extensions import TypedDict @@ -38,6 +28,7 @@ from synapse.http.server import HttpServer, finish_request from synapse.http.servlet import ( RestServlet, assert_params_in_dict, + parse_boolean, parse_bytes_from_args, parse_json_object_from_request, parse_string, @@ -72,7 +63,7 @@ class LoginRestServlet(RestServlet): JWT_TYPE_DEPRECATED = "m.login.jwt" APPSERVICE_TYPE = "m.login.application_service" APPSERVICE_TYPE_UNSTABLE = "uk.half-shot.msc2778.login.application_service" - REFRESH_TOKEN_PARAM = "refresh_token" + REFRESH_TOKEN_PARAM = "org.matrix.msc2918.refresh_token" def __init__(self, hs: "HomeServer"): super().__init__() @@ -90,7 +81,7 @@ class LoginRestServlet(RestServlet): self.saml2_enabled = hs.config.saml2.saml2_enabled self.cas_enabled = hs.config.cas.cas_enabled self.oidc_enabled = hs.config.oidc.oidc_enabled - self._refresh_tokens_enabled = ( + self._msc2918_enabled = ( hs.config.registration.refreshable_access_token_lifetime is not None ) @@ -163,16 +154,14 @@ class LoginRestServlet(RestServlet): async def on_POST(self, request: SynapseRequest) -> Tuple[int, LoginResponse]: login_submission = parse_json_object_from_request(request) - # Check to see if the client requested a refresh token. - client_requested_refresh_token = login_submission.get( - LoginRestServlet.REFRESH_TOKEN_PARAM, False - ) - if not isinstance(client_requested_refresh_token, bool): - raise SynapseError(400, "`refresh_token` should be true or false.") - - should_issue_refresh_token = ( - self._refresh_tokens_enabled and client_requested_refresh_token - ) + if self._msc2918_enabled: + # Check if this login should also issue a refresh token, as per + # MSC2918 + should_issue_refresh_token = parse_boolean( + request, name=LoginRestServlet.REFRESH_TOKEN_PARAM, default=False + ) + else: + should_issue_refresh_token = False try: if login_submission["type"] in ( @@ -302,7 +291,6 @@ class LoginRestServlet(RestServlet): ratelimit: bool = True, auth_provider_id: Optional[str] = None, should_issue_refresh_token: bool = False, - auth_provider_session_id: Optional[str] = None, ) -> LoginResponse: """Called when we've successfully authed the user and now need to actually login them in (e.g. create devices). This gets called on @@ -318,10 +306,10 @@ class LoginRestServlet(RestServlet): create_non_existent_users: Whether to create the user if they don't exist. Defaults to False. ratelimit: Whether to ratelimit the login request. - auth_provider_id: The SSO IdP the user used, if any. + auth_provider_id: The SSO IdP the user used, if any (just used for the + prometheus metrics). should_issue_refresh_token: True if this login should issue a refresh token alongside the access token. - auth_provider_session_id: The session ID got during login from the SSO IdP. Returns: result: Dictionary of account information after successful login. @@ -354,7 +342,6 @@ class LoginRestServlet(RestServlet): initial_display_name, auth_provider_id=auth_provider_id, should_issue_refresh_token=should_issue_refresh_token, - auth_provider_session_id=auth_provider_session_id, ) result = LoginResponse( @@ -400,7 +387,6 @@ class LoginRestServlet(RestServlet): self.auth_handler._sso_login_callback, auth_provider_id=res.auth_provider_id, should_issue_refresh_token=should_issue_refresh_token, - auth_provider_session_id=res.auth_provider_session_id, ) async def _do_jwt_login( @@ -462,7 +448,9 @@ def _get_auth_flow_dict_for_idp(idp: SsoIdentityProvider) -> JsonDict: class RefreshTokenServlet(RestServlet): - PATTERNS = (re.compile("^/_matrix/client/v1/refresh$"),) + PATTERNS = client_patterns( + "/org.matrix.msc2918.refresh_token/refresh$", releases=(), unstable=True + ) def __init__(self, hs: "HomeServer"): self._auth_handler = hs.get_auth_handler() @@ -470,7 +458,6 @@ class RefreshTokenServlet(RestServlet): self.refreshable_access_token_lifetime = ( hs.config.registration.refreshable_access_token_lifetime ) - self.refresh_token_lifetime = hs.config.registration.refresh_token_lifetime async def on_POST(self, request: SynapseRequest) -> Tuple[int, JsonDict]: refresh_submission = parse_json_object_from_request(request) @@ -480,32 +467,21 @@ class RefreshTokenServlet(RestServlet): if not isinstance(token, str): raise SynapseError(400, "Invalid param: refresh_token", Codes.INVALID_PARAM) - now = self._clock.time_msec() - access_valid_until_ms = None - if self.refreshable_access_token_lifetime is not None: - access_valid_until_ms = now + self.refreshable_access_token_lifetime - refresh_valid_until_ms = None - if self.refresh_token_lifetime is not None: - refresh_valid_until_ms = now + self.refresh_token_lifetime - - ( - access_token, - refresh_token, - actual_access_token_expiry, - ) = await self._auth_handler.refresh_token( - token, access_valid_until_ms, refresh_valid_until_ms + valid_until_ms = ( + self._clock.time_msec() + self.refreshable_access_token_lifetime + ) + access_token, refresh_token = await self._auth_handler.refresh_token( + token, valid_until_ms + ) + expires_in_ms = valid_until_ms - self._clock.time_msec() + return ( + 200, + { + "access_token": access_token, + "refresh_token": refresh_token, + "expires_in_ms": expires_in_ms, + }, ) - - response: Dict[str, Union[str, int]] = { - "access_token": access_token, - "refresh_token": refresh_token, - } - - # expires_in_ms is only present if the token expires - if actual_access_token_expiry is not None: - response["expires_in_ms"] = actual_access_token_expiry - now - - return 200, response class SsoRedirectServlet(RestServlet): @@ -513,7 +489,7 @@ class SsoRedirectServlet(RestServlet): re.compile( "^" + CLIENT_API_PREFIX - + "/(r0|v3)/login/sso/redirect/(?P<idp_id>[A-Za-z0-9_.~-]+)$" + + "/r0/login/sso/redirect/(?P<idp_id>[A-Za-z0-9_.~-]+)$" ) ] diff --git a/synapse/rest/client/register.py b/synapse/rest/client/register.py index 8b56c76aed..d2b11e39d9 100644 --- a/synapse/rest/client/register.py +++ b/synapse/rest/client/register.py @@ -41,6 +41,7 @@ from synapse.http.server import HttpServer, finish_request, respond_with_html from synapse.http.servlet import ( RestServlet, assert_params_in_dict, + parse_boolean, parse_json_object_from_request, parse_string, ) @@ -419,7 +420,7 @@ class RegisterRestServlet(RestServlet): self.password_policy_handler = hs.get_password_policy_handler() self.clock = hs.get_clock() self._registration_enabled = self.hs.config.registration.enable_registration - self._refresh_tokens_enabled = ( + self._msc2918_enabled = ( hs.config.registration.refreshable_access_token_lifetime is not None ) @@ -445,15 +446,14 @@ class RegisterRestServlet(RestServlet): f"Do not understand membership kind: {kind}", ) - # Check if the clients wishes for this registration to issue a refresh - # token. - client_requested_refresh_tokens = body.get("refresh_token", False) - if not isinstance(client_requested_refresh_tokens, bool): - raise SynapseError(400, "`refresh_token` should be true or false.") - - should_issue_refresh_token = ( - self._refresh_tokens_enabled and client_requested_refresh_tokens - ) + if self._msc2918_enabled: + # Check if this registration should also issue a refresh token, as + # per MSC2918 + should_issue_refresh_token = parse_boolean( + request, name="org.matrix.msc2918.refresh_token", default=False + ) + else: + should_issue_refresh_token = False # Pull out the provided username and do basic sanity checks early since # the auth layer will store these in sessions. diff --git a/synapse/rest/client/relations.py b/synapse/rest/client/relations.py index fc4e6921c5..45e9f1dd90 100644 --- a/synapse/rest/client/relations.py +++ b/synapse/rest/client/relations.py @@ -224,14 +224,18 @@ class RelationPaginationServlet(RestServlet): ) now = self.clock.time_msec() - # Do not bundle aggregations when retrieving the original event because - # we want the content before relations are applied to it. + # We set bundle_relations to False when retrieving the original + # event because we want the content before relations were applied to + # it. original_event = await self._event_serializer.serialize_event( - event, now, bundle_aggregations=False + event, now, bundle_relations=False + ) + # Similarly, we don't allow relations to be applied to relations, so we + # return the original relations without any aggregations on top of them + # here. + serialized_events = await self._event_serializer.serialize_events( + events, now, bundle_relations=False ) - # The relations returned for the requested event do include their - # bundled aggregations. - serialized_events = await self._event_serializer.serialize_events(events, now) return_value = pagination_chunk.to_dict() return_value["chunk"] = serialized_events diff --git a/synapse/rest/client/room.py b/synapse/rest/client/room.py index f48e2e6ca2..955d4e8641 100644 --- a/synapse/rest/client/room.py +++ b/synapse/rest/client/room.py @@ -716,7 +716,10 @@ class RoomEventContextServlet(RestServlet): results["events_after"], time_now ) results["state"] = await self._event_serializer.serialize_events( - results["state"], time_now + results["state"], + time_now, + # No need to bundle aggregations for state events + bundle_relations=False, ) return 200, results @@ -1067,62 +1070,6 @@ def register_txn_path( ) -class TimestampLookupRestServlet(RestServlet): - """ - API endpoint to fetch the `event_id` of the closest event to the given - timestamp (`ts` query parameter) in the given direction (`dir` query - parameter). - - Useful for cases like jump to date so you can start paginating messages from - a given date in the archive. - - `ts` is a timestamp in milliseconds where we will find the closest event in - the given direction. - - `dir` can be `f` or `b` to indicate forwards and backwards in time from the - given timestamp. - - GET /_matrix/client/unstable/org.matrix.msc3030/rooms/<roomID>/timestamp_to_event?ts=<timestamp>&dir=<direction> - { - "event_id": ... - } - """ - - PATTERNS = ( - re.compile( - "^/_matrix/client/unstable/org.matrix.msc3030" - "/rooms/(?P<room_id>[^/]*)/timestamp_to_event$" - ), - ) - - def __init__(self, hs: "HomeServer"): - super().__init__() - self._auth = hs.get_auth() - self._store = hs.get_datastore() - self.timestamp_lookup_handler = hs.get_timestamp_lookup_handler() - - async def on_GET( - self, request: SynapseRequest, room_id: str - ) -> Tuple[int, JsonDict]: - requester = await self._auth.get_user_by_req(request) - await self._auth.check_user_in_room(room_id, requester.user.to_string()) - - timestamp = parse_integer(request, "ts", required=True) - direction = parse_string(request, "dir", default="f", allowed_values=["f", "b"]) - - ( - event_id, - origin_server_ts, - ) = await self.timestamp_lookup_handler.get_event_for_timestamp( - requester, room_id, timestamp, direction - ) - - return 200, { - "event_id": event_id, - "origin_server_ts": origin_server_ts, - } - - class RoomSpaceSummaryRestServlet(RestServlet): PATTERNS = ( re.compile( @@ -1193,7 +1140,7 @@ class RoomSpaceSummaryRestServlet(RestServlet): class RoomHierarchyRestServlet(RestServlet): PATTERNS = ( re.compile( - "^/_matrix/client/(v1|unstable/org.matrix.msc2946)" + "^/_matrix/client/unstable/org.matrix.msc2946" "/rooms/(?P<room_id>[^/]*)/hierarchy$" ), ) @@ -1221,7 +1168,7 @@ class RoomHierarchyRestServlet(RestServlet): ) return 200, await self._room_summary_handler.get_room_hierarchy( - requester, + requester.user.to_string(), room_id, suggested_only=parse_boolean(request, "suggested_only", default=False), max_depth=max_depth, @@ -1292,8 +1239,6 @@ def register_servlets( RoomAliasListServlet(hs).register(http_server) SearchRestServlet(hs).register(http_server) RoomCreateRestServlet(hs).register(http_server) - if hs.config.experimental.msc3030_enabled: - TimestampLookupRestServlet(hs).register(http_server) # Some servlets only get registered for the main process. if not is_worker: diff --git a/synapse/rest/client/sync.py b/synapse/rest/client/sync.py index 88e4f5e063..b6a2485732 100644 --- a/synapse/rest/client/sync.py +++ b/synapse/rest/client/sync.py @@ -520,9 +520,9 @@ class SyncRestServlet(RestServlet): return self._event_serializer.serialize_events( events, time_now=time_now, - # Don't bother to bundle aggregations if the timeline is unlimited, - # as clients will have all the necessary information. - bundle_aggregations=room.timeline.limited, + # We don't bundle "live" events, as otherwise clients + # will end up double counting annotations. + bundle_relations=False, token_id=token_id, event_format=event_formatter, only_event_fields=only_fields, diff --git a/synapse/rest/media/v1/filepath.py b/synapse/rest/media/v1/filepath.py index 1f6441c412..c0e15c6513 100644 --- a/synapse/rest/media/v1/filepath.py +++ b/synapse/rest/media/v1/filepath.py @@ -43,75 +43,47 @@ GetPathMethod = TypeVar( ) -def _wrap_with_jail_check(relative: bool) -> Callable[[GetPathMethod], GetPathMethod]: +def _wrap_with_jail_check(func: GetPathMethod) -> GetPathMethod: """Wraps a path-returning method to check that the returned path(s) do not escape the media store directory. - The path-returning method may return either a single path, or a list of paths. - The check is not expected to ever fail, unless `func` is missing a call to `_validate_path_component`, or `_validate_path_component` is buggy. Args: - relative: A boolean indicating whether the wrapped method returns paths relative - to the media store directory. + func: The `MediaFilePaths` method to wrap. The method may return either a single + path, or a list of paths. Returned paths may be either absolute or relative. Returns: - A method which will wrap a path-returning method, adding a check to ensure that - the returned path(s) lie within the media store directory. The check will raise - a `ValueError` if it fails. + The method, wrapped with a check to ensure that the returned path(s) lie within + the media store directory. Raises a `ValueError` if the check fails. """ - def _wrap_with_jail_check_inner(func: GetPathMethod) -> GetPathMethod: - @functools.wraps(func) - def _wrapped( - self: "MediaFilePaths", *args: Any, **kwargs: Any - ) -> Union[str, List[str]]: - path_or_paths = func(self, *args, **kwargs) - - if isinstance(path_or_paths, list): - paths_to_check = path_or_paths - else: - paths_to_check = [path_or_paths] - - for path in paths_to_check: - # Construct the path that will ultimately be used. - # We cannot guess whether `path` is relative to the media store - # directory, since the media store directory may itself be a relative - # path. - if relative: - path = os.path.join(self.base_path, path) - normalized_path = os.path.normpath(path) - - # Now that `normpath` has eliminated `../`s and `./`s from the path, - # `os.path.commonpath` can be used to check whether it lies within the - # media store directory. - if ( - os.path.commonpath([normalized_path, self.normalized_base_path]) - != self.normalized_base_path - ): - # The path resolves to outside the media store directory, - # or `self.base_path` is `.`, which is an unlikely configuration. - raise ValueError(f"Invalid media store path: {path!r}") - - # Note that `os.path.normpath`/`abspath` has a subtle caveat: - # `a/b/c/../c` will normalize to `a/b/c`, but the former refers to a - # different path if `a/b/c` is a symlink. That is, the check above is - # not perfect and may allow a certain restricted subset of untrustworthy - # paths through. Since the check above is secondary to the main - # `_validate_path_component` checks, it's less important for it to be - # perfect. - # - # As an alternative, `os.path.realpath` will resolve symlinks, but - # proves problematic if there are symlinks inside the media store. - # eg. if `url_store/` is symlinked to elsewhere, its canonical path - # won't match that of the main media store directory. - - return path_or_paths - - return cast(GetPathMethod, _wrapped) - - return _wrap_with_jail_check_inner + @functools.wraps(func) + def _wrapped( + self: "MediaFilePaths", *args: Any, **kwargs: Any + ) -> Union[str, List[str]]: + path_or_paths = func(self, *args, **kwargs) + + if isinstance(path_or_paths, list): + paths_to_check = path_or_paths + else: + paths_to_check = [path_or_paths] + + for path in paths_to_check: + # path may be an absolute or relative path, depending on the method being + # wrapped. When "appending" an absolute path, `os.path.join` discards the + # previous path, which is desired here. + normalized_path = os.path.normpath(os.path.join(self.real_base_path, path)) + if ( + os.path.commonpath([normalized_path, self.real_base_path]) + != self.real_base_path + ): + raise ValueError(f"Invalid media store path: {path!r}") + + return path_or_paths + + return cast(GetPathMethod, _wrapped) ALLOWED_CHARACTERS = set( @@ -155,7 +127,9 @@ class MediaFilePaths: def __init__(self, primary_base_path: str): self.base_path = primary_base_path - self.normalized_base_path = os.path.normpath(self.base_path) + + # The media store directory, with all symlinks resolved. + self.real_base_path = os.path.realpath(primary_base_path) # Refuse to initialize if paths cannot be validated correctly for the current # platform. @@ -166,7 +140,7 @@ class MediaFilePaths: # for certain homeservers there, since ":"s aren't allowed in paths. assert os.name == "posix" - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def local_media_filepath_rel(self, media_id: str) -> str: return os.path.join( "local_content", @@ -177,7 +151,7 @@ class MediaFilePaths: local_media_filepath = _wrap_in_base_path(local_media_filepath_rel) - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def local_media_thumbnail_rel( self, media_id: str, width: int, height: int, content_type: str, method: str ) -> str: @@ -193,7 +167,7 @@ class MediaFilePaths: local_media_thumbnail = _wrap_in_base_path(local_media_thumbnail_rel) - @_wrap_with_jail_check(relative=False) + @_wrap_with_jail_check def local_media_thumbnail_dir(self, media_id: str) -> str: """ Retrieve the local store path of thumbnails of a given media_id @@ -211,7 +185,7 @@ class MediaFilePaths: _validate_path_component(media_id[4:]), ) - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def remote_media_filepath_rel(self, server_name: str, file_id: str) -> str: return os.path.join( "remote_content", @@ -223,7 +197,7 @@ class MediaFilePaths: remote_media_filepath = _wrap_in_base_path(remote_media_filepath_rel) - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def remote_media_thumbnail_rel( self, server_name: str, @@ -249,7 +223,7 @@ class MediaFilePaths: # Legacy path that was used to store thumbnails previously. # Should be removed after some time, when most of the thumbnails are stored # using the new path. - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def remote_media_thumbnail_rel_legacy( self, server_name: str, file_id: str, width: int, height: int, content_type: str ) -> str: @@ -264,7 +238,6 @@ class MediaFilePaths: _validate_path_component(file_name), ) - @_wrap_with_jail_check(relative=False) def remote_media_thumbnail_dir(self, server_name: str, file_id: str) -> str: return os.path.join( self.base_path, @@ -275,7 +248,7 @@ class MediaFilePaths: _validate_path_component(file_id[4:]), ) - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def url_cache_filepath_rel(self, media_id: str) -> str: if NEW_FORMAT_ID_RE.match(media_id): # Media id is of the form <DATE><RANDOM_STRING> @@ -295,7 +268,7 @@ class MediaFilePaths: url_cache_filepath = _wrap_in_base_path(url_cache_filepath_rel) - @_wrap_with_jail_check(relative=False) + @_wrap_with_jail_check def url_cache_filepath_dirs_to_delete(self, media_id: str) -> List[str]: "The dirs to try and remove if we delete the media_id file" if NEW_FORMAT_ID_RE.match(media_id): @@ -317,7 +290,7 @@ class MediaFilePaths: ), ] - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def url_cache_thumbnail_rel( self, media_id: str, width: int, height: int, content_type: str, method: str ) -> str: @@ -345,7 +318,7 @@ class MediaFilePaths: url_cache_thumbnail = _wrap_in_base_path(url_cache_thumbnail_rel) - @_wrap_with_jail_check(relative=True) + @_wrap_with_jail_check def url_cache_thumbnail_directory_rel(self, media_id: str) -> str: # Media id is of the form <DATE><RANDOM_STRING> # E.g.: 2017-09-28-fsdRDt24DS234dsf @@ -368,7 +341,7 @@ class MediaFilePaths: url_cache_thumbnail_directory_rel ) - @_wrap_with_jail_check(relative=False) + @_wrap_with_jail_check def url_cache_thumbnail_dirs_to_delete(self, media_id: str) -> List[str]: "The dirs to try and remove if we delete the media_id thumbnails" # Media id is of the form <DATE><RANDOM_STRING> |