Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 755bfee

Browse files
authored
Use servlets for /key/ endpoints. (#14229)
To fix the response for unknown endpoints under that prefix. See MSC3743.
1 parent da2c93d commit 755bfee

File tree

9 files changed

+86
-83
lines changed

9 files changed

+86
-83
lines changed

changelog.d/14229.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor `/key/` endpoints to use `RestServlet` classes.

synapse/api/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
FEDERATION_V2_PREFIX = FEDERATION_PREFIX + "/v2"
2929
FEDERATION_UNSTABLE_PREFIX = FEDERATION_PREFIX + "/unstable"
3030
STATIC_PREFIX = "/_matrix/static"
31-
SERVER_KEY_V2_PREFIX = "/_matrix/key/v2"
31+
SERVER_KEY_PREFIX = "/_matrix/key"
3232
MEDIA_R0_PREFIX = "/_matrix/media/r0"
3333
MEDIA_V3_PREFIX = "/_matrix/media/v3"
3434
LEGACY_MEDIA_PREFIX = "/_matrix/media/v1"

synapse/app/generic_worker.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
LEGACY_MEDIA_PREFIX,
2929
MEDIA_R0_PREFIX,
3030
MEDIA_V3_PREFIX,
31-
SERVER_KEY_V2_PREFIX,
31+
SERVER_KEY_PREFIX,
3232
)
3333
from synapse.app import _base
3434
from synapse.app._base import (
@@ -89,7 +89,7 @@
8989
RegistrationTokenValidityRestServlet,
9090
)
9191
from synapse.rest.health import HealthResource
92-
from synapse.rest.key.v2 import KeyApiV2Resource
92+
from synapse.rest.key.v2 import KeyResource
9393
from synapse.rest.synapse.client import build_synapse_client_resource_tree
9494
from synapse.rest.well_known import well_known_resource
9595
from synapse.server import HomeServer
@@ -325,13 +325,13 @@ def _listen_http(self, listener_config: ListenerConfig) -> None:
325325

326326
presence.register_servlets(self, resource)
327327

328-
resources.update({CLIENT_API_PREFIX: resource})
328+
resources[CLIENT_API_PREFIX] = resource
329329

330330
resources.update(build_synapse_client_resource_tree(self))
331-
resources.update({"/.well-known": well_known_resource(self)})
331+
resources["/.well-known"] = well_known_resource(self)
332332

333333
elif name == "federation":
334-
resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
334+
resources[FEDERATION_PREFIX] = TransportLayerServer(self)
335335
elif name == "media":
336336
if self.config.media.can_load_media_repo:
337337
media_repo = self.get_media_repository_resource()
@@ -359,16 +359,12 @@ def _listen_http(self, listener_config: ListenerConfig) -> None:
359359
# Only load the openid resource separately if federation resource
360360
# is not specified since federation resource includes openid
361361
# resource.
362-
resources.update(
363-
{
364-
FEDERATION_PREFIX: TransportLayerServer(
365-
self, servlet_groups=["openid"]
366-
)
367-
}
362+
resources[FEDERATION_PREFIX] = TransportLayerServer(
363+
self, servlet_groups=["openid"]
368364
)
369365

370366
if name in ["keys", "federation"]:
371-
resources[SERVER_KEY_V2_PREFIX] = KeyApiV2Resource(self)
367+
resources[SERVER_KEY_PREFIX] = KeyResource(self)
372368

373369
if name == "replication":
374370
resources[REPLICATION_PREFIX] = ReplicationRestResource(self)

synapse/app/homeserver.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
LEGACY_MEDIA_PREFIX,
3232
MEDIA_R0_PREFIX,
3333
MEDIA_V3_PREFIX,
34-
SERVER_KEY_V2_PREFIX,
34+
SERVER_KEY_PREFIX,
3535
STATIC_PREFIX,
3636
)
3737
from synapse.app import _base
@@ -60,7 +60,7 @@
6060
from synapse.rest import ClientRestResource
6161
from synapse.rest.admin import AdminRestResource
6262
from synapse.rest.health import HealthResource
63-
from synapse.rest.key.v2 import KeyApiV2Resource
63+
from synapse.rest.key.v2 import KeyResource
6464
from synapse.rest.synapse.client import build_synapse_client_resource_tree
6565
from synapse.rest.well_known import well_known_resource
6666
from synapse.server import HomeServer
@@ -215,30 +215,22 @@ def _configure_named_resource(
215215
consent_resource: Resource = ConsentResource(self)
216216
if compress:
217217
consent_resource = gz_wrap(consent_resource)
218-
resources.update({"/_matrix/consent": consent_resource})
218+
resources["/_matrix/consent"] = consent_resource
219219

220220
if name == "federation":
221221
federation_resource: Resource = TransportLayerServer(self)
222222
if compress:
223223
federation_resource = gz_wrap(federation_resource)
224-
resources.update({FEDERATION_PREFIX: federation_resource})
224+
resources[FEDERATION_PREFIX] = federation_resource
225225

226226
if name == "openid":
227-
resources.update(
228-
{
229-
FEDERATION_PREFIX: TransportLayerServer(
230-
self, servlet_groups=["openid"]
231-
)
232-
}
227+
resources[FEDERATION_PREFIX] = TransportLayerServer(
228+
self, servlet_groups=["openid"]
233229
)
234230

235231
if name in ["static", "client"]:
236-
resources.update(
237-
{
238-
STATIC_PREFIX: StaticResource(
239-
os.path.join(os.path.dirname(synapse.__file__), "static")
240-
)
241-
}
232+
resources[STATIC_PREFIX] = StaticResource(
233+
os.path.join(os.path.dirname(synapse.__file__), "static")
242234
)
243235

244236
if name in ["media", "federation", "client"]:
@@ -257,7 +249,7 @@ def _configure_named_resource(
257249
)
258250

259251
if name in ["keys", "federation"]:
260-
resources[SERVER_KEY_V2_PREFIX] = KeyApiV2Resource(self)
252+
resources[SERVER_KEY_PREFIX] = KeyResource(self)
261253

262254
if name == "metrics" and self.config.metrics.enable_metrics:
263255
metrics_resource: Resource = MetricsResource(RegistryProxy)

synapse/rest/key/v2/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@
1414

1515
from typing import TYPE_CHECKING
1616

17-
from twisted.web.resource import Resource
18-
19-
from .local_key_resource import LocalKey
20-
from .remote_key_resource import RemoteKey
17+
from synapse.http.server import HttpServer, JsonResource
18+
from synapse.rest.key.v2.local_key_resource import LocalKey
19+
from synapse.rest.key.v2.remote_key_resource import RemoteKey
2120

2221
if TYPE_CHECKING:
2322
from synapse.server import HomeServer
2423

2524

26-
class KeyApiV2Resource(Resource):
25+
class KeyResource(JsonResource):
2726
def __init__(self, hs: "HomeServer"):
28-
Resource.__init__(self)
29-
self.putChild(b"server", LocalKey(hs))
30-
self.putChild(b"query", RemoteKey(hs))
27+
super().__init__(hs, canonical_json=True)
28+
self.register_servlets(self, hs)
29+
30+
@staticmethod
31+
def register_servlets(http_server: HttpServer, hs: "HomeServer") -> None:
32+
LocalKey(hs).register(http_server)
33+
RemoteKey(hs).register(http_server)

synapse/rest/key/v2/local_key_resource.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,15 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from typing import TYPE_CHECKING, Optional
16+
import re
17+
from typing import TYPE_CHECKING, Optional, Tuple
1718

18-
from canonicaljson import encode_canonical_json
1919
from signedjson.sign import sign_json
2020
from unpaddedbase64 import encode_base64
2121

22-
from twisted.web.resource import Resource
22+
from twisted.web.server import Request
2323

24-
from synapse.http.server import respond_with_json_bytes
25-
from synapse.http.site import SynapseRequest
24+
from synapse.http.servlet import RestServlet
2625
from synapse.types import JsonDict
2726

2827
if TYPE_CHECKING:
@@ -31,7 +30,7 @@
3130
logger = logging.getLogger(__name__)
3231

3332

34-
class LocalKey(Resource):
33+
class LocalKey(RestServlet):
3534
"""HTTP resource containing encoding the TLS X.509 certificate and NACL
3635
signature verification keys for this server::
3736
@@ -61,18 +60,17 @@ class LocalKey(Resource):
6160
}
6261
"""
6362

64-
isLeaf = True
63+
PATTERNS = (re.compile("^/_matrix/key/v2/server(/(?P<key_id>[^/]*))?$"),)
6564

6665
def __init__(self, hs: "HomeServer"):
6766
self.config = hs.config
6867
self.clock = hs.get_clock()
6968
self.update_response_body(self.clock.time_msec())
70-
Resource.__init__(self)
7169

7270
def update_response_body(self, time_now_msec: int) -> None:
7371
refresh_interval = self.config.key.key_refresh_interval
7472
self.valid_until_ts = int(time_now_msec + refresh_interval)
75-
self.response_body = encode_canonical_json(self.response_json_object())
73+
self.response_body = self.response_json_object()
7674

7775
def response_json_object(self) -> JsonDict:
7876
verify_keys = {}
@@ -99,9 +97,11 @@ def response_json_object(self) -> JsonDict:
9997
json_object = sign_json(json_object, self.config.server.server_name, key)
10098
return json_object
10199

102-
def render_GET(self, request: SynapseRequest) -> Optional[int]:
100+
def on_GET(
101+
self, request: Request, key_id: Optional[str] = None
102+
) -> Tuple[int, JsonDict]:
103103
time_now = self.clock.time_msec()
104104
# Update the expiry time if less than half the interval remains.
105105
if time_now + self.config.key.key_refresh_interval / 2 > self.valid_until_ts:
106106
self.update_response_body(time_now)
107-
return respond_with_json_bytes(request, 200, self.response_body)
107+
return 200, self.response_body

synapse/rest/key/v2/remote_key_resource.py

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,20 @@
1313
# limitations under the License.
1414

1515
import logging
16-
from typing import TYPE_CHECKING, Dict, Set
16+
import re
17+
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
1718

1819
from signedjson.sign import sign_json
1920

20-
from synapse.api.errors import Codes, SynapseError
21+
from twisted.web.server import Request
22+
2123
from synapse.crypto.keyring import ServerKeyFetcher
22-
from synapse.http.server import DirectServeJsonResource, respond_with_json
23-
from synapse.http.servlet import parse_integer, parse_json_object_from_request
24-
from synapse.http.site import SynapseRequest
24+
from synapse.http.server import HttpServer
25+
from synapse.http.servlet import (
26+
RestServlet,
27+
parse_integer,
28+
parse_json_object_from_request,
29+
)
2530
from synapse.types import JsonDict
2631
from synapse.util import json_decoder
2732
from synapse.util.async_helpers import yieldable_gather_results
@@ -32,7 +37,7 @@
3237
logger = logging.getLogger(__name__)
3338

3439

35-
class RemoteKey(DirectServeJsonResource):
40+
class RemoteKey(RestServlet):
3641
"""HTTP resource for retrieving the TLS certificate and NACL signature
3742
verification keys for a collection of servers. Checks that the reported
3843
X.509 TLS certificate matches the one used in the HTTPS connection. Checks
@@ -88,11 +93,7 @@ class RemoteKey(DirectServeJsonResource):
8893
}
8994
"""
9095

91-
isLeaf = True
92-
9396
def __init__(self, hs: "HomeServer"):
94-
super().__init__()
95-
9697
self.fetcher = ServerKeyFetcher(hs)
9798
self.store = hs.get_datastores().main
9899
self.clock = hs.get_clock()
@@ -101,36 +102,48 @@ def __init__(self, hs: "HomeServer"):
101102
)
102103
self.config = hs.config
103104

104-
async def _async_render_GET(self, request: SynapseRequest) -> None:
105-
assert request.postpath is not None
106-
if len(request.postpath) == 1:
107-
(server,) = request.postpath
108-
query: dict = {server.decode("ascii"): {}}
109-
elif len(request.postpath) == 2:
110-
server, key_id = request.postpath
105+
def register(self, http_server: HttpServer) -> None:
106+
http_server.register_paths(
107+
"GET",
108+
(
109+
re.compile(
110+
"^/_matrix/key/v2/query/(?P<server>[^/]*)(/(?P<key_id>[^/]*))?$"
111+
),
112+
),
113+
self.on_GET,
114+
self.__class__.__name__,
115+
)
116+
http_server.register_paths(
117+
"POST",
118+
(re.compile("^/_matrix/key/v2/query$"),),
119+
self.on_POST,
120+
self.__class__.__name__,
121+
)
122+
123+
async def on_GET(
124+
self, request: Request, server: str, key_id: Optional[str] = None
125+
) -> Tuple[int, JsonDict]:
126+
if server and key_id:
111127
minimum_valid_until_ts = parse_integer(request, "minimum_valid_until_ts")
112128
arguments = {}
113129
if minimum_valid_until_ts is not None:
114130
arguments["minimum_valid_until_ts"] = minimum_valid_until_ts
115-
query = {server.decode("ascii"): {key_id.decode("ascii"): arguments}}
131+
query = {server: {key_id: arguments}}
116132
else:
117-
raise SynapseError(404, "Not found %r" % request.postpath, Codes.NOT_FOUND)
133+
query = {server: {}}
118134

119-
await self.query_keys(request, query, query_remote_on_cache_miss=True)
135+
return 200, await self.query_keys(query, query_remote_on_cache_miss=True)
120136

121-
async def _async_render_POST(self, request: SynapseRequest) -> None:
137+
async def on_POST(self, request: Request) -> Tuple[int, JsonDict]:
122138
content = parse_json_object_from_request(request)
123139

124140
query = content["server_keys"]
125141

126-
await self.query_keys(request, query, query_remote_on_cache_miss=True)
142+
return 200, await self.query_keys(query, query_remote_on_cache_miss=True)
127143

128144
async def query_keys(
129-
self,
130-
request: SynapseRequest,
131-
query: JsonDict,
132-
query_remote_on_cache_miss: bool = False,
133-
) -> None:
145+
self, query: JsonDict, query_remote_on_cache_miss: bool = False
146+
) -> JsonDict:
134147
logger.info("Handling query for keys %r", query)
135148

136149
store_queries = []
@@ -232,7 +245,7 @@ async def query_keys(
232245
for server_name, keys in cache_misses.items()
233246
),
234247
)
235-
await self.query_keys(request, query, query_remote_on_cache_miss=False)
248+
return await self.query_keys(query, query_remote_on_cache_miss=False)
236249
else:
237250
signed_keys = []
238251
for key_json_raw in json_results:
@@ -244,6 +257,4 @@ async def query_keys(
244257

245258
signed_keys.append(key_json)
246259

247-
response = {"server_keys": signed_keys}
248-
249-
respond_with_json(request, 200, response, canonical_json=True)
260+
return {"server_keys": signed_keys}

tests/app/test_openid_listener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_openid_listener(self, names, expectation):
7979
self.assertEqual(channel.code, 401)
8080

8181

82-
@patch("synapse.app.homeserver.KeyApiV2Resource", new=Mock())
82+
@patch("synapse.app.homeserver.KeyResource", new=Mock())
8383
class SynapseHomeserverOpenIDListenerTests(HomeserverTestCase):
8484
def make_homeserver(self, reactor, clock):
8585
hs = self.setup_test_homeserver(

tests/rest/key/v2/test_remote_key_resource.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from synapse.crypto.keyring import PerspectivesKeyFetcher
2828
from synapse.http.site import SynapseRequest
29-
from synapse.rest.key.v2 import KeyApiV2Resource
29+
from synapse.rest.key.v2 import KeyResource
3030
from synapse.server import HomeServer
3131
from synapse.storage.keys import FetchKeyResult
3232
from synapse.types import JsonDict
@@ -46,7 +46,7 @@ def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer:
4646

4747
def create_test_resource(self) -> Resource:
4848
return create_resource_tree(
49-
{"/_matrix/key/v2": KeyApiV2Resource(self.hs)}, root_resource=NoResource()
49+
{"/_matrix/key/v2": KeyResource(self.hs)}, root_resource=NoResource()
5050
)
5151

5252
def expect_outgoing_key_request(

0 commit comments

Comments
 (0)