Repository: matrix-org/synapse

Issue:
Internal server error when looking up a invalid room alias 
https://sentry.tools.element.io/organizations/element/issues/923/?project=2&query=is%3Aunresolved

```
ValueError: At least one of the arguments destination and destination_is must be a nonempty bytestring.
  File "synapse/http/server.py", line 366, in _async_render_wrapper
    callback_return = await self._async_render(request)
  File "synapse/http/server.py", line 572, in _async_render
    callback_return = await raw_callback_return
  File "synapse/rest/client/directory.py", line 57, in on_GET
    res = await self.directory_handler.get_association(room_alias_obj)
  File "synapse/handlers/directory.py", line 262, in get_association
    fed_result: Optional[JsonDict] = await self.federation.make_query(
  File "synapse/federation/federation_client.py", line 183, in make_query
    return await self.transport_layer.make_query(
  File "synapse/federation/transport/client.py", line 260, in make_query
    return await self.client.get_json(
  File "synapse/http/matrixfederationclient.py", line 1069, in get_json
    response = await self._send_request_with_optional_trailing_slash(
  File "synapse/http/matrixfederationclient.py", line 405, in _send_request_with_optional_trailing_slash
    response = await self._send_request(request, **send_request_args)
  File "synapse/http/matrixfederationclient.py", line 555, in _send_request
    auth_headers = self.build_auth_headers(
  File "synapse/http/matrixfederationclient.py", line 735, in build_auth_headers
    raise ValueError(
```

This came from a GET request for `url: /_matrix/client/r0/directory/room/%23Computer-security%3A, `.

That's an invalid room alias because it doesn't contain a domain name. Very similar to #12690, then.


Discussion:
To fix:
- There are three calls to `RoomAlias.from_string` in `synapse.rest.client.directory`.
- These should only be called if `RoomAlias.is_valid` confirms that the given alias is valid.
- If not, raise a `SynapseError`. Consult the [CS spec](https://spec.matrix.org/v1.3/client-server-api/) to see what HTTP status code and what matrix [error code](https://spec.matrix.org/v1.3/client-server-api/#common-error-codes) is appropriate.
@DMRobertson I'll work on this!

Resolving patch:
diff --git a/synapse/rest/client/directory.py b/synapse/rest/client/directory.py
--- a/synapse/rest/client/directory.py
+++ b/synapse/rest/client/directory.py
@@ -46,6 +46,8 @@ def __init__(self, hs: "HomeServer"):
         self.auth = hs.get_auth()
 
     async def on_GET(self, request: Request, room_alias: str) -> Tuple[int, JsonDict]:
+        if not RoomAlias.is_valid(room_alias):
+            raise SynapseError(400, "Room alias invalid", errcode=Codes.INVALID_PARAM)
         room_alias_obj = RoomAlias.from_string(room_alias)
 
         res = await self.directory_handler.get_association(room_alias_obj)
@@ -55,6 +57,8 @@ async def on_GET(self, request: Request, room_alias: str) -> Tuple[int, JsonDict
     async def on_PUT(
         self, request: SynapseRequest, room_alias: str
     ) -> Tuple[int, JsonDict]:
+        if not RoomAlias.is_valid(room_alias):
+            raise SynapseError(400, "Room alias invalid", errcode=Codes.INVALID_PARAM)
         room_alias_obj = RoomAlias.from_string(room_alias)
 
         content = parse_json_object_from_request(request)
@@ -89,6 +93,8 @@ async def on_PUT(
     async def on_DELETE(
         self, request: SynapseRequest, room_alias: str
     ) -> Tuple[int, JsonDict]:
+        if not RoomAlias.is_valid(room_alias):
+            raise SynapseError(400, "Room alias invalid", errcode=Codes.INVALID_PARAM)
         room_alias_obj = RoomAlias.from_string(room_alias)
         requester = await self.auth.get_user_by_req(request)
 


Test patch:
diff --git a/tests/rest/client/test_directory.py b/tests/rest/client/test_directory.py
--- a/tests/rest/client/test_directory.py
+++ b/tests/rest/client/test_directory.py
@@ -215,6 +215,19 @@ def set_alias_via_directory(
         self.assertEqual(channel.code, expected_code, channel.result)
         return alias
 
+    def test_invalid_alias(self) -> None:
+        alias = "#potato"
+        channel = self.make_request(
+            "GET",
+            f"/_matrix/client/r0/directory/room/{alias}",
+            access_token=self.user_tok,
+        )
+        self.assertEqual(channel.code, HTTPStatus.BAD_REQUEST, channel.result)
+        self.assertIn("error", channel.json_body, channel.json_body)
+        self.assertEqual(
+            channel.json_body["errcode"], "M_INVALID_PARAM", channel.json_body
+        )
+
     def random_alias(self, length: int) -> str:
         return RoomAlias(random_string(length), self.hs.hostname).to_string()