mirror of
https://github.com/element-hq/synapse.git
synced 2025-12-19 02:20:44 +00:00
Compare commits
4 Commits
develop
...
travis/pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe47dc16c7 | ||
|
|
abbefc89d6 | ||
|
|
b679e8cf57 | ||
|
|
c93de04bc5 |
1
changelog.d/19314.feature
Normal file
1
changelog.d/19314.feature
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Add experimental support for the [MSC4370](https://github.com/matrix-org/matrix-spec-proposals/pull/4370) Federation API `GET /extremities` endpoint.
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
# Copyright (C) 2023 New Vector, Ltd
|
# Copyright (C) 2023 New Vector, Ltd
|
||||||
|
# Copyright (C) 2025 Element Creations Ltd
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@@ -533,6 +534,9 @@ class ExperimentalConfig(Config):
|
|||||||
"msc4108_delegation_endpoint", None
|
"msc4108_delegation_endpoint", None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# MSC4370: Get extremities federation endpoint
|
||||||
|
self.msc4370_enabled = experimental.get("msc4370_enabled", False)
|
||||||
|
|
||||||
auth_delegated = self.msc3861.enabled or (
|
auth_delegated = self.msc3861.enabled or (
|
||||||
config.get("matrix_authentication_service") or {}
|
config.get("matrix_authentication_service") or {}
|
||||||
).get("enabled", False)
|
).get("enabled", False)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# Copyright 2019-2021 Matrix.org Federation C.I.C
|
# Copyright 2019-2021 Matrix.org Federation C.I.C
|
||||||
# Copyright 2015, 2016 OpenMarket Ltd
|
# Copyright 2015, 2016 OpenMarket Ltd
|
||||||
# Copyright (C) 2023 New Vector, Ltd
|
# Copyright (C) 2023 New Vector, Ltd
|
||||||
|
# Copyright (C) 2025 Element Creations Ltd
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@@ -683,6 +684,16 @@ class FederationServer(FederationBase):
|
|||||||
resp = await self.registry.on_query(query_type, args)
|
resp = await self.registry.on_query(query_type, args)
|
||||||
return 200, resp
|
return 200, resp
|
||||||
|
|
||||||
|
async def on_get_extremities_request(self, origin: str, room_id: str) -> JsonDict:
|
||||||
|
origin_host, _ = parse_server_name(origin)
|
||||||
|
await self.check_server_matches_acl(origin_host, room_id)
|
||||||
|
|
||||||
|
await self._event_auth_handler.assert_host_in_room(room_id, origin)
|
||||||
|
|
||||||
|
extremities = await self.store.get_forward_extremities_for_room(room_id)
|
||||||
|
prev_event_ids = [e[0] for e in extremities]
|
||||||
|
return {"prev_events": prev_event_ids}
|
||||||
|
|
||||||
async def on_make_join_request(
|
async def on_make_join_request(
|
||||||
self, origin: str, room_id: str, user_id: str, supported_versions: list[str]
|
self, origin: str, room_id: str, user_id: str, supported_versions: list[str]
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
# Copyright 2020 Sorunome
|
# Copyright 2020 Sorunome
|
||||||
# Copyright 2014-2021 The Matrix.org Foundation C.I.C.
|
# Copyright 2014-2021 The Matrix.org Foundation C.I.C.
|
||||||
# Copyright (C) 2023 New Vector, Ltd
|
# Copyright (C) 2023 New Vector, Ltd
|
||||||
|
# Copyright (C) 2025 Element Creations Ltd
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@@ -33,6 +34,7 @@ from synapse.federation.transport.server.federation import (
|
|||||||
FederationMediaDownloadServlet,
|
FederationMediaDownloadServlet,
|
||||||
FederationMediaThumbnailServlet,
|
FederationMediaThumbnailServlet,
|
||||||
FederationUnstableClientKeysClaimServlet,
|
FederationUnstableClientKeysClaimServlet,
|
||||||
|
FederationUnstableGetExtremitiesServlet,
|
||||||
)
|
)
|
||||||
from synapse.http.server import HttpServer, JsonResource
|
from synapse.http.server import HttpServer, JsonResource
|
||||||
from synapse.http.servlet import (
|
from synapse.http.servlet import (
|
||||||
@@ -326,6 +328,12 @@ def register_servlets(
|
|||||||
if not hs.config.media.can_load_media_repo:
|
if not hs.config.media.can_load_media_repo:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if (
|
||||||
|
servletclass == FederationUnstableGetExtremitiesServlet
|
||||||
|
and not hs.config.experimental.msc4370_enabled
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
servletclass(
|
servletclass(
|
||||||
hs=hs,
|
hs=hs,
|
||||||
authenticator=authenticator,
|
authenticator=authenticator,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
# Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
# Copyright (C) 2023 New Vector, Ltd
|
# Copyright (C) 2023 New Vector, Ltd
|
||||||
|
# Copyright (C) 2025 Element Creations Ltd
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as
|
# it under the terms of the GNU Affero General Public License as
|
||||||
@@ -273,6 +274,22 @@ class FederationQueryServlet(BaseFederationServerServlet):
|
|||||||
return await self.handler.on_query_request(query_type, args)
|
return await self.handler.on_query_request(query_type, args)
|
||||||
|
|
||||||
|
|
||||||
|
class FederationUnstableGetExtremitiesServlet(BaseFederationServerServlet):
|
||||||
|
PREFIX = FEDERATION_UNSTABLE_PREFIX + "/org.matrix.msc4370"
|
||||||
|
PATH = "/extremities/(?P<room_id>[^/]*)"
|
||||||
|
CATEGORY = "Federation requests"
|
||||||
|
|
||||||
|
async def on_GET(
|
||||||
|
self,
|
||||||
|
origin: str,
|
||||||
|
content: Literal[None],
|
||||||
|
query: dict[bytes, list[bytes]],
|
||||||
|
room_id: str,
|
||||||
|
) -> tuple[int, JsonDict]:
|
||||||
|
result = await self.handler.on_get_extremities_request(origin, room_id)
|
||||||
|
return 200, result
|
||||||
|
|
||||||
|
|
||||||
class FederationMakeJoinServlet(BaseFederationServerServlet):
|
class FederationMakeJoinServlet(BaseFederationServerServlet):
|
||||||
PATH = "/make_join/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
|
PATH = "/make_join/(?P<room_id>[^/]*)/(?P<user_id>[^/]*)"
|
||||||
CATEGORY = "Federation requests"
|
CATEGORY = "Federation requests"
|
||||||
@@ -884,6 +901,7 @@ FEDERATION_SERVLET_CLASSES: tuple[type[BaseFederationServlet], ...] = (
|
|||||||
FederationBackfillServlet,
|
FederationBackfillServlet,
|
||||||
FederationTimestampLookupServlet,
|
FederationTimestampLookupServlet,
|
||||||
FederationQueryServlet,
|
FederationQueryServlet,
|
||||||
|
FederationUnstableGetExtremitiesServlet,
|
||||||
FederationMakeJoinServlet,
|
FederationMakeJoinServlet,
|
||||||
FederationMakeLeaveServlet,
|
FederationMakeLeaveServlet,
|
||||||
FederationEventServlet,
|
FederationEventServlet,
|
||||||
|
|||||||
@@ -324,6 +324,148 @@ class StateQueryTests(unittest.FederatingHomeserverTestCase):
|
|||||||
self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
|
self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
|
||||||
|
|
||||||
|
|
||||||
|
class UnstableGetExtremitiesTests(unittest.FederatingHomeserverTestCase):
|
||||||
|
servlets = [
|
||||||
|
admin.register_servlets,
|
||||||
|
room.register_servlets,
|
||||||
|
login.register_servlets,
|
||||||
|
]
|
||||||
|
|
||||||
|
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||||
|
super().prepare(reactor, clock, hs)
|
||||||
|
self._storage_controllers = hs.get_storage_controllers()
|
||||||
|
|
||||||
|
def _make_endpoint_path(self, room_id: str) -> str:
|
||||||
|
return f"/_matrix/federation/unstable/org.matrix.msc4370/extremities/{room_id}"
|
||||||
|
|
||||||
|
def _remote_join(self, room_id: str, room_version: str) -> None:
|
||||||
|
# Note: other tests ensure the called endpoints in this function return useful
|
||||||
|
# and proper data.
|
||||||
|
|
||||||
|
# make_join first
|
||||||
|
joining_user = "@misspiggy:" + self.OTHER_SERVER_NAME
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET",
|
||||||
|
f"/_matrix/federation/v1/make_join/{room_id}/{joining_user}?ver={room_version}",
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
join_result = channel.json_body
|
||||||
|
|
||||||
|
# Sign/populate the join
|
||||||
|
join_event_dict = join_result["event"]
|
||||||
|
self.add_hashes_and_signatures_from_other_server(
|
||||||
|
join_event_dict,
|
||||||
|
KNOWN_ROOM_VERSIONS[room_version],
|
||||||
|
)
|
||||||
|
if room_version in ["1", "2"]:
|
||||||
|
add_hashes_and_signatures(
|
||||||
|
KNOWN_ROOM_VERSIONS[room_version],
|
||||||
|
join_event_dict,
|
||||||
|
signature_name=self.hs.hostname,
|
||||||
|
signing_key=self.hs.signing_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send the join
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"PUT",
|
||||||
|
f"/_matrix/federation/v2/send_join/{room_id}/x",
|
||||||
|
content=join_event_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that things went okay so the test doesn't become a total train wreck
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
r = self.get_success(self._storage_controllers.state.get_current_state(room_id))
|
||||||
|
self.assertEqual(r[("m.room.member", joining_user)].membership, "join")
|
||||||
|
|
||||||
|
def _test_get_extremities_common(self, room_version: str) -> None:
|
||||||
|
# Create a room to test with
|
||||||
|
creator_user_id = self.register_user("kermit", "test")
|
||||||
|
tok = self.login("kermit", "test")
|
||||||
|
room_id = self.helper.create_room_as(
|
||||||
|
room_creator=creator_user_id,
|
||||||
|
tok=tok,
|
||||||
|
room_version=room_version,
|
||||||
|
extra_content={
|
||||||
|
# Public preset uses `shared` history visibility, but makes joins
|
||||||
|
# easier in our tests.
|
||||||
|
# https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3createroom
|
||||||
|
"preset": "public_chat"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# At this stage we should fail to get the extremities because we're not joined
|
||||||
|
# and therefore can't see the events (`shared` history visibility).
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET", self._make_endpoint_path(room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.FORBIDDEN, channel.json_body)
|
||||||
|
self.assertEqual(channel.json_body["error"], "Host not in room.")
|
||||||
|
self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
|
||||||
|
|
||||||
|
# Now join the room and try again
|
||||||
|
# Note: we're expecting a linear room DAG, so there should be just one extremity
|
||||||
|
self._remote_join(room_id, room_version)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET", self._make_endpoint_path(room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body)
|
||||||
|
self.assertEqual(
|
||||||
|
channel.json_body["prev_events"],
|
||||||
|
[
|
||||||
|
self.get_success(
|
||||||
|
self._storage_controllers.main.get_forward_extremities_for_room(
|
||||||
|
room_id
|
||||||
|
)
|
||||||
|
)[0][0]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# ACL the calling server and try again. This should cause an error getting extremities.
|
||||||
|
self.helper.send_state(
|
||||||
|
room_id,
|
||||||
|
"m.room.server_acl",
|
||||||
|
{
|
||||||
|
"allow": ["*"],
|
||||||
|
"allow_ip_literals": False,
|
||||||
|
"deny": [self.OTHER_SERVER_NAME],
|
||||||
|
},
|
||||||
|
tok=tok,
|
||||||
|
expect_code=HTTPStatus.OK,
|
||||||
|
)
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET", self._make_endpoint_path(room_id)
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.FORBIDDEN, channel.json_body)
|
||||||
|
self.assertEqual(channel.json_body["error"], "Server is banned from room")
|
||||||
|
self.assertEqual(channel.json_body["errcode"], "M_FORBIDDEN")
|
||||||
|
|
||||||
|
@parameterized.expand([(k,) for k in KNOWN_ROOM_VERSIONS.keys()])
|
||||||
|
@override_config(
|
||||||
|
{"use_frozen_dicts": True, "experimental_features": {"msc4370_enabled": True}}
|
||||||
|
)
|
||||||
|
def test_get_extremities_with_frozen_dicts(self, room_version: str) -> None:
|
||||||
|
"""Test GET /extremities with USE_FROZEN_DICTS=True"""
|
||||||
|
self._test_get_extremities_common(room_version)
|
||||||
|
|
||||||
|
@parameterized.expand([(k,) for k in KNOWN_ROOM_VERSIONS.keys()])
|
||||||
|
@override_config(
|
||||||
|
{"use_frozen_dicts": False, "experimental_features": {"msc4370_enabled": True}}
|
||||||
|
)
|
||||||
|
def test_get_extremities_without_frozen_dicts(self, room_version: str) -> None:
|
||||||
|
"""Test GET /extremities with USE_FROZEN_DICTS=True"""
|
||||||
|
self._test_get_extremities_common(room_version)
|
||||||
|
|
||||||
|
# note the lack of config-setting stuff on this test.
|
||||||
|
def test_get_extremities_unstable_not_enabled(self) -> None:
|
||||||
|
"""Test that GET /extremities returns M_UNRECOGNIZED when MSC4370 is not enabled"""
|
||||||
|
# We shouldn't even have to create a room - the endpoint should just fail.
|
||||||
|
channel = self.make_signed_federation_request(
|
||||||
|
"GET", self._make_endpoint_path("!room:example.org")
|
||||||
|
)
|
||||||
|
self.assertEqual(channel.code, HTTPStatus.NOT_FOUND, channel.json_body)
|
||||||
|
self.assertEqual(channel.json_body["errcode"], "M_UNRECOGNIZED")
|
||||||
|
|
||||||
|
|
||||||
class SendJoinFederationTests(unittest.FederatingHomeserverTestCase):
|
class SendJoinFederationTests(unittest.FederatingHomeserverTestCase):
|
||||||
servlets = [
|
servlets = [
|
||||||
admin.register_servlets,
|
admin.register_servlets,
|
||||||
|
|||||||
Reference in New Issue
Block a user