Use Pydantic when PUTting room aliases (#14179)

This commit is contained in:
David Robertson 2022-10-17 13:39:12 +01:00 committed by GitHub
parent 2c2c3f8b2c
commit ccce8cdfc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 31 deletions

View File

@ -0,0 +1 @@
Improve the validation of the following PUT endpoints: [`/directory/room/{roomAlias}`](https://spec.matrix.org/v1.4/client-server-api/#put_matrixclientv3directoryroomroomalias), [`/directory/list/room/{roomId}`](https://spec.matrix.org/v1.4/client-server-api/#put_matrixclientv3directorylistroomroomid) and [`/directory/list/appservice/{networkId}/{roomId}`](https://spec.matrix.org/v1.4/application-service-api/#put_matrixclientv3directorylistappservicenetworkidroomid).

View File

@ -16,6 +16,8 @@ import logging
import string import string
from typing import TYPE_CHECKING, Iterable, List, Optional from typing import TYPE_CHECKING, Iterable, List, Optional
from typing_extensions import Literal
from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes from synapse.api.constants import MAX_ALIAS_LENGTH, EventTypes
from synapse.api.errors import ( from synapse.api.errors import (
AuthError, AuthError,
@ -429,7 +431,10 @@ class DirectoryHandler:
return await self.auth.check_can_change_room_list(room_id, requester) return await self.auth.check_can_change_room_list(room_id, requester)
async def edit_published_room_list( async def edit_published_room_list(
self, requester: Requester, room_id: str, visibility: str self,
requester: Requester,
room_id: str,
visibility: Literal["public", "private"],
) -> None: ) -> None:
"""Edit the entry of the room in the published room list. """Edit the entry of the room in the published room list.
@ -451,9 +456,6 @@ class DirectoryHandler:
if requester.is_guest: if requester.is_guest:
raise AuthError(403, "Guests cannot edit the published room list") raise AuthError(403, "Guests cannot edit the published room list")
if visibility not in ["public", "private"]:
raise SynapseError(400, "Invalid visibility setting")
if visibility == "public" and not self.enable_room_list_search: if visibility == "public" and not self.enable_room_list_search:
# The room list has been disabled. # The room list has been disabled.
raise AuthError( raise AuthError(
@ -505,7 +507,11 @@ class DirectoryHandler:
await self.store.set_room_is_public(room_id, making_public) await self.store.set_room_is_public(room_id, making_public)
async def edit_published_appservice_room_list( async def edit_published_appservice_room_list(
self, appservice_id: str, network_id: str, room_id: str, visibility: str self,
appservice_id: str,
network_id: str,
room_id: str,
visibility: Literal["public", "private"],
) -> None: ) -> None:
"""Add or remove a room from the appservice/network specific public """Add or remove a room from the appservice/network specific public
room list. room list.
@ -516,9 +522,6 @@ class DirectoryHandler:
room_id room_id
visibility: either "public" or "private" visibility: either "public" or "private"
""" """
if visibility not in ["public", "private"]:
raise SynapseError(400, "Invalid visibility setting")
await self.store.set_room_is_public_appservice( await self.store.set_room_is_public_appservice(
room_id, appservice_id, network_id, visibility == "public" room_id, appservice_id, network_id, visibility == "public"
) )

View File

@ -13,15 +13,22 @@
# limitations under the License. # limitations under the License.
import logging import logging
from typing import TYPE_CHECKING, Tuple from typing import TYPE_CHECKING, List, Optional, Tuple
from pydantic import StrictStr
from typing_extensions import Literal
from twisted.web.server import Request from twisted.web.server import Request
from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError from synapse.api.errors import AuthError, Codes, NotFoundError, SynapseError
from synapse.http.server import HttpServer from synapse.http.server import HttpServer
from synapse.http.servlet import RestServlet, parse_json_object_from_request from synapse.http.servlet import (
RestServlet,
parse_and_validate_json_object_from_request,
)
from synapse.http.site import SynapseRequest from synapse.http.site import SynapseRequest
from synapse.rest.client._base import client_patterns from synapse.rest.client._base import client_patterns
from synapse.rest.models import RequestBodyModel
from synapse.types import JsonDict, RoomAlias from synapse.types import JsonDict, RoomAlias
if TYPE_CHECKING: if TYPE_CHECKING:
@ -54,6 +61,12 @@ class ClientDirectoryServer(RestServlet):
return 200, res return 200, res
class PutBody(RequestBodyModel):
# TODO: get Pydantic to validate that this is a valid room id?
room_id: StrictStr
# `servers` is unspecced
servers: Optional[List[StrictStr]] = None
async def on_PUT( async def on_PUT(
self, request: SynapseRequest, room_alias: str self, request: SynapseRequest, room_alias: str
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
@ -61,31 +74,22 @@ class ClientDirectoryServer(RestServlet):
raise SynapseError(400, "Room alias invalid", errcode=Codes.INVALID_PARAM) raise SynapseError(400, "Room alias invalid", errcode=Codes.INVALID_PARAM)
room_alias_obj = RoomAlias.from_string(room_alias) room_alias_obj = RoomAlias.from_string(room_alias)
content = parse_json_object_from_request(request) content = parse_and_validate_json_object_from_request(request, self.PutBody)
if "room_id" not in content:
raise SynapseError(
400, 'Missing params: ["room_id"]', errcode=Codes.BAD_JSON
)
logger.debug("Got content: %s", content) logger.debug("Got content: %s", content)
logger.debug("Got room name: %s", room_alias_obj.to_string()) logger.debug("Got room name: %s", room_alias_obj.to_string())
room_id = content["room_id"] logger.debug("Got room_id: %s", content.room_id)
servers = content["servers"] if "servers" in content else None logger.debug("Got servers: %s", content.servers)
logger.debug("Got room_id: %s", room_id) room = await self.store.get_room(content.room_id)
logger.debug("Got servers: %s", servers)
# TODO(erikj): Check types.
room = await self.store.get_room(room_id)
if room is None: if room is None:
raise SynapseError(400, "Room does not exist") raise SynapseError(400, "Room does not exist")
requester = await self.auth.get_user_by_req(request) requester = await self.auth.get_user_by_req(request)
await self.directory_handler.create_association( await self.directory_handler.create_association(
requester, room_alias_obj, room_id, servers requester, room_alias_obj, content.room_id, content.servers
) )
return 200, {} return 200, {}
@ -137,16 +141,18 @@ class ClientDirectoryListServer(RestServlet):
return 200, {"visibility": "public" if room["is_public"] else "private"} return 200, {"visibility": "public" if room["is_public"] else "private"}
class PutBody(RequestBodyModel):
visibility: Literal["public", "private"] = "public"
async def on_PUT( async def on_PUT(
self, request: SynapseRequest, room_id: str self, request: SynapseRequest, room_id: str
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
requester = await self.auth.get_user_by_req(request) requester = await self.auth.get_user_by_req(request)
content = parse_json_object_from_request(request) content = parse_and_validate_json_object_from_request(request, self.PutBody)
visibility = content.get("visibility", "public")
await self.directory_handler.edit_published_room_list( await self.directory_handler.edit_published_room_list(
requester, room_id, visibility requester, room_id, content.visibility
) )
return 200, {} return 200, {}
@ -163,12 +169,14 @@ class ClientAppserviceDirectoryListServer(RestServlet):
self.directory_handler = hs.get_directory_handler() self.directory_handler = hs.get_directory_handler()
self.auth = hs.get_auth() self.auth = hs.get_auth()
class PutBody(RequestBodyModel):
visibility: Literal["public", "private"] = "public"
async def on_PUT( async def on_PUT(
self, request: SynapseRequest, network_id: str, room_id: str self, request: SynapseRequest, network_id: str, room_id: str
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
content = parse_json_object_from_request(request) content = parse_and_validate_json_object_from_request(request, self.PutBody)
visibility = content.get("visibility", "public") return await self._edit(request, network_id, room_id, content.visibility)
return await self._edit(request, network_id, room_id, visibility)
async def on_DELETE( async def on_DELETE(
self, request: SynapseRequest, network_id: str, room_id: str self, request: SynapseRequest, network_id: str, room_id: str
@ -176,7 +184,11 @@ class ClientAppserviceDirectoryListServer(RestServlet):
return await self._edit(request, network_id, room_id, "private") return await self._edit(request, network_id, room_id, "private")
async def _edit( async def _edit(
self, request: SynapseRequest, network_id: str, room_id: str, visibility: str self,
request: SynapseRequest,
network_id: str,
room_id: str,
visibility: Literal["public", "private"],
) -> Tuple[int, JsonDict]: ) -> Tuple[int, JsonDict]:
requester = await self.auth.get_user_by_req(request) requester = await self.auth.get_user_by_req(request)
if not requester.app_service: if not requester.app_service: