Now called MSC4388

This commit is contained in:
Hugh Nimmo-Smith
2025-12-12 10:13:30 +00:00
parent 9979d126d8
commit a6caec1adc
10 changed files with 70 additions and 74 deletions

View File

@@ -137,7 +137,7 @@ fn get_runtime<'a>(reactor: &Bound<'a, PyAny>) -> PyResult<PyRef<'a, PyTokioRunt
static DEFER: OnceCell<PyObject> = OnceCell::new();
/// Access to the `twisted.internet.defer` module.
fn defer(py: Python<'_>) -> PyResult<&Bound<PyAny>> {
fn defer(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
Ok(DEFER
.get_or_try_init(|| py.import("twisted.internet.defer").map(Into::into))?
.bind(py))

View File

@@ -11,7 +11,7 @@ pub mod http;
pub mod http_client;
pub mod identifier;
pub mod matrix_const;
pub mod msc4108v2025_rendezvous;
pub mod msc4388_rendezvous;
pub mod push;
pub mod rendezvous;
pub mod segmenter;
@@ -55,7 +55,7 @@ fn synapse_rust(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
events::register_module(py, m)?;
http_client::register_module(py, m)?;
rendezvous::register_module(py, m)?;
msc4108v2025_rendezvous::register_module(py, m)?;
msc4388_rendezvous::register_module(py, m)?;
segmenter::register_module(py, m)?;
Ok(())

View File

@@ -58,7 +58,7 @@ fn prepare_headers(headers: &mut HeaderMap) {
}
#[pyclass]
struct MSC4108v2025RendezvousHandler {
struct MSC4388RendezvousHandler {
clock: PyObject,
sessions: BTreeMap<Ulid, Session>,
capacity: usize,
@@ -66,7 +66,7 @@ struct MSC4108v2025RendezvousHandler {
ttl: Duration,
}
impl MSC4108v2025RendezvousHandler {
impl MSC4388RendezvousHandler {
/// Check the length of the data parameter and throw error if invalid.
fn check_data_length(&self, data: &str) -> PyResult<()> {
let data_length = data.len() as u64;
@@ -98,7 +98,7 @@ impl MSC4108v2025RendezvousHandler {
}
#[pymethods]
impl MSC4108v2025RendezvousHandler {
impl MSC4388RendezvousHandler {
#[new]
#[pyo3(signature = (homeserver, /, capacity=100, max_content_length=4*1024, eviction_interval=60*1000, ttl=2*60*1000))]
fn new(
@@ -324,7 +324,7 @@ impl MSC4108v2025RendezvousHandler {
return Err(SynapseError::new(
StatusCode::CONFLICT,
"sequence_token does not match".to_owned(),
"IO_ELEMENT_MSC4108_CONCURRENT_WRITE",
"IO_ELEMENT_MSC4388_CONCURRENT_WRITE",
None,
Some(headers),
));
@@ -365,9 +365,9 @@ impl MSC4108v2025RendezvousHandler {
}
pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
let child_module = PyModule::new(py, "msc4108v2025_rendezvous")?;
let child_module = PyModule::new(py, "msc4388_rendezvous")?;
child_module.add_class::<MSC4108v2025RendezvousHandler>()?;
child_module.add_class::<MSC4388RendezvousHandler>()?;
m.add_submodule(&child_module)?;
@@ -375,7 +375,7 @@ pub fn register_module(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()>
// synapse.synapse_rust import rendezvous` work.
py.import("sys")?
.getattr("modules")?
.set_item("synapse.synapse_rust.msc4108v2025_rendezvous", child_module)?;
.set_item("synapse.synapse_rust.msc4388_rendezvous", child_module)?;
Ok(())
}

View File

@@ -548,25 +548,23 @@ class ExperimentalConfig(Config):
("experimental", "msc4108_delegation_endpoint"),
)
# MSC4108: Mechanism to allow OAuth 2.0 API sign in and E2EE set up via QR code - 2025 version:
msc4108v2025_mode = experimental.get("msc4108v2025_mode", "off")
# MSC4388: Secure out-of-band channel for sign in with QR:
msc4388_mode = experimental.get("msc4388_mode", "off")
if ["off", "public", "authenticated"].count(msc4108v2025_mode) != 1:
if ["off", "public", "authenticated"].count(msc4388_mode) != 1:
raise ConfigError(
"msc4108v2025_mode must be one of 'off', 'public' or 'authenticated'",
("experimental", "msc4108v2025_mode"),
"msc4388_mode must be one of 'off', 'public' or 'authenticated'",
("experimental", "msc4388_mode"),
)
self.msc4108v2025_enabled: bool = msc4108v2025_mode != "off"
self.msc4108v2025_requires_authentication: bool = (
msc4108v2025_mode == "authenticated"
)
self.msc4388_enabled: bool = msc4388_mode != "off"
self.msc4388_requires_authentication: bool = msc4388_mode == "authenticated"
if self.msc4108v2025_enabled and not (
if self.msc4388_enabled and not (
config.get("matrix_authentication_service") or {}
).get("enabled", False):
raise ConfigError(
"MSC4108 2025 version requires matrix_authentication_service to be enabled",
("experimental", "msc4108v2025_enabled"),
"MSC4388 requires matrix_authentication_service to be enabled",
("experimental", "msc4388_enabled"),
)
# MSC4133: Custom profile fields

View File

@@ -68,17 +68,17 @@ class MSC4108RendezvousServlet(RestServlet):
self._handler.handle_post(request)
class MSC4108v2025CreateRendezvousServlet(RestServlet):
class MSC4388CreateRendezvousServlet(RestServlet):
PATTERNS = client_patterns(
"/io.element.msc4108/rendezvous$", releases=[], v1=False, unstable=True
"/io.element.msc4388/rendezvous$", releases=[], v1=False, unstable=True
)
def __init__(self, hs: "HomeServer") -> None:
super().__init__()
self._handler = hs.get_msc4108v2025_rendezvous_handler()
self._handler = hs.get_msc4388_rendezvous_handler()
self.auth = hs.get_auth()
self.require_authentication = (
hs.config.experimental.msc4108v2025_requires_authentication
hs.config.experimental.msc4388_requires_authentication
)
async def on_POST(self, request: SynapseRequest) -> None:
@@ -88,9 +88,9 @@ class MSC4108v2025CreateRendezvousServlet(RestServlet):
self._handler.handle_post(request)
class MSC4108v2025UpdateRendezvousServlet(RestServlet):
class MSC4388UpdateRendezvousServlet(RestServlet):
PATTERNS = client_patterns(
"/io.element.msc4108/rendezvous/(?P<rendezvous_id>[^/]+)$",
"/io.element.msc4388/rendezvous/(?P<rendezvous_id>[^/]+)$",
releases=[],
v1=False,
unstable=True,
@@ -98,7 +98,7 @@ class MSC4108v2025UpdateRendezvousServlet(RestServlet):
def __init__(self, hs: "HomeServer") -> None:
super().__init__()
self._handler = hs.get_msc4108v2025_rendezvous_handler()
self._handler = hs.get_msc4388_rendezvous_handler()
def on_GET(self, request: SynapseRequest, rendezvous_id: str) -> None:
self._handler.handle_get(request, rendezvous_id)
@@ -117,6 +117,6 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
if hs.config.experimental.msc4108_delegation_endpoint is not None:
MSC4108DelegationRendezvousServlet(hs).register(http_server)
if hs.config.experimental.msc4108v2025_enabled:
MSC4108v2025CreateRendezvousServlet(hs).register(http_server)
MSC4108v2025UpdateRendezvousServlet(hs).register(http_server)
if hs.config.experimental.msc4388_enabled:
MSC4388CreateRendezvousServlet(hs).register(http_server)
MSC4388UpdateRendezvousServlet(hs).register(http_server)

View File

@@ -169,10 +169,8 @@ class VersionsRestServlet(RestServlet):
is not None
)
),
# MSC4108: Mechanism to allow OIDC sign in and E2EE set up via QR code - 2025 version
"io.element.msc4108": (
self.config.experimental.msc4108v2025_enabled
),
# MSC4388: Secure out-of-band channel for sign in with QR
"io.element.msc4388": (self.config.experimental.msc4388_enabled),
# MSC4140: Delayed events
"org.matrix.msc4140": bool(self.config.server.max_event_delay_ms),
# Simplified sliding sync

View File

@@ -170,7 +170,7 @@ from synapse.state import StateHandler, StateResolutionHandler
from synapse.storage import Databases
from synapse.storage.controllers import StorageControllers
from synapse.streams.events import EventSources
from synapse.synapse_rust.msc4108v2025_rendezvous import MSC4108v2025RendezvousHandler
from synapse.synapse_rust.msc4388_rendezvous import MSC4388RendezvousHandler
from synapse.synapse_rust.rendezvous import RendezvousHandler
from synapse.types import DomainSpecificString, ISynapseReactor
from synapse.util import SYNAPSE_VERSION
@@ -1158,8 +1158,8 @@ class HomeServer(metaclass=abc.ABCMeta):
return RendezvousHandler(self)
@cache_in_self
def get_msc4108v2025_rendezvous_handler(self) -> MSC4108v2025RendezvousHandler:
return MSC4108v2025RendezvousHandler(self)
def get_msc4388_rendezvous_handler(self) -> MSC4388RendezvousHandler:
return MSC4388RendezvousHandler(self)
@cache_in_self
def get_outbound_redis_connection(self) -> "ConnectionHandler":

View File

@@ -14,15 +14,15 @@ from twisted.web.iweb import IRequest
from synapse.server import HomeServer
class MSC4108v2025RendezvousHandler:
class MSC4388RendezvousHandler:
def __init__(
self,
homeserver: HomeServer,
/,
capacity: int = 100, # This should be configurable
max_content_length: int = 4 * 1024, # MSC4108 specifies maximum of 4KB
max_content_length: int = 4 * 1024, # MSC4388 specifies maximum of 4KB
eviction_interval: int = 60 * 1000,
ttl: int = 2 * 60 * 1000, # MSC4108 specifies minimum of 120 seconds
ttl: int = 2 * 60 * 1000, # MSC4388 specifies minimum of 120 seconds
) -> None: ...
def handle_post(self, request: IRequest) -> None: ...
def handle_get(self, request: IRequest, session_id: str) -> None: ...

View File

@@ -29,12 +29,12 @@ from tests import unittest
from tests.unittest import checked_cast, override_config
from tests.utils import HAS_AUTHLIB
msc4108_endpoint = "/_matrix/client/unstable/io.element.msc4108/rendezvous"
rz_endpoint = "/_matrix/client/unstable/io.element.msc4388/rendezvous"
class RendezvousServletTestCase(unittest.HomeserverTestCase):
"""
Test the experimental MSC4108 rendezvous endpoint with the latest behaviour.
Test the experimental MSC4388 rendezvous endpoint.
"""
servlets = [
@@ -121,18 +121,18 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
return "mock_token_" + username + "_" + device_id
def test_disabled(self) -> None:
channel = self.make_request("POST", msc4108_endpoint, {}, access_token=None)
channel = self.make_request("POST", rz_endpoint, {}, access_token=None)
self.assertEqual(channel.code, 404)
@override_config(
{
"experimental_features": {
"msc4108v2025_mode": "off",
"msc4388_mode": "off",
},
}
)
def test_off(self) -> None:
channel = self.make_request("POST", msc4108_endpoint, {}, access_token=None)
channel = self.make_request("POST", rz_endpoint, {}, access_token=None)
self.assertEqual(channel.code, 404)
@unittest.skip_unless(HAS_AUTHLIB, "requires authlib")
@@ -145,7 +145,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -161,7 +161,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# We can post arbitrary data to the endpoint
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
@@ -171,7 +171,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
expires_ts = channel.json_body["expires_ts"]
self.assertGreater(expires_ts, self.hs.get_clock().time_msec())
session_endpoint = msc4108_endpoint + f"/{rendezvous_id}"
session_endpoint = rz_endpoint + f"/{rendezvous_id}"
# We can get the data back
channel = self.make_request(
@@ -207,7 +207,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
self.assertEqual(channel.code, 409)
self.assertEqual(
channel.json_body["errcode"], "IO_ELEMENT_MSC4108_CONCURRENT_WRITE"
channel.json_body["errcode"], "IO_ELEMENT_MSC4388_CONCURRENT_WRITE"
)
# We should get the updated data
@@ -251,7 +251,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "authenticated",
"msc4388_mode": "authenticated",
},
}
)
@@ -270,7 +270,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# This should fail without authentication:
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
@@ -279,7 +279,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# This should work as we are now authenticated
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=alice_token,
)
@@ -289,7 +289,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
expires_ts = channel.json_body["expires_ts"]
self.assertGreater(expires_ts, self.hs.get_clock().time_msec())
session_endpoint = msc4108_endpoint + f"/{rendezvous_id}"
session_endpoint = rz_endpoint + f"/{rendezvous_id}"
# We can get the data back without authentication
channel = self.make_request(
@@ -355,7 +355,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -366,12 +366,12 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# Start a new session
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
self.assertEqual(channel.code, 200)
session_endpoint = msc4108_endpoint + "/" + channel.json_body["id"]
session_endpoint = rz_endpoint + "/" + channel.json_body["id"]
# Sanity check that we can get the data back
channel = self.make_request(
@@ -403,7 +403,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -415,12 +415,12 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# Start a new session
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
self.assertEqual(channel.code, 200)
session_endpoint = msc4108_endpoint + "/" + channel.json_body["id"]
session_endpoint = rz_endpoint + "/" + channel.json_body["id"]
# Sanity check that we can get the data back
channel = self.make_request(
@@ -438,7 +438,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
for _ in range(100):
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
@@ -475,7 +475,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -487,12 +487,12 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# Start a new session
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
self.assertEqual(channel.code, 200)
session_endpoint = msc4108_endpoint + "/" + channel.json_body["id"]
session_endpoint = rz_endpoint + "/" + channel.json_body["id"]
# We advance the clock to make sure that this entry is the "lowest" in the session list
self.reactor.advance(1)
@@ -509,7 +509,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
for _ in range(200):
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "foo=bar"},
access_token=None,
)
@@ -534,7 +534,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -548,7 +548,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
for invalid_data in invalid_datas:
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": invalid_data},
access_token=None,
)
@@ -558,7 +558,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# Make a valid request
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "test"},
access_token=None,
)
@@ -566,7 +566,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
rendezvous_id = channel.json_body["id"]
sequence_token = channel.json_body["sequence_token"]
session_endpoint = msc4108_endpoint + f"/{rendezvous_id}"
session_endpoint = rz_endpoint + f"/{rendezvous_id}"
# We can't update the data with invalid data
for invalid_data in invalid_datas:
@@ -589,7 +589,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
"endpoint": "https://issuer",
},
"experimental_features": {
"msc4108v2025_mode": "public",
"msc4388_mode": "public",
},
}
)
@@ -601,7 +601,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": too_long_data},
access_token=None,
)
@@ -611,7 +611,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
# Make a valid request
channel = self.make_request(
"POST",
msc4108_endpoint,
rz_endpoint,
{"data": "test"},
access_token=None,
)
@@ -619,7 +619,7 @@ class RendezvousServletTestCase(unittest.HomeserverTestCase):
rendezvous_id = channel.json_body["id"]
sequence_token = channel.json_body["sequence_token"]
session_endpoint = msc4108_endpoint + f"/{rendezvous_id}"
session_endpoint = rz_endpoint + f"/{rendezvous_id}"
# We can't update the data with invalid data
channel = self.make_request(